JSZip and cfs:collection in Meteor app - meteor

So, Im using udondan:jszip, cfs:collection,
cfs:standard-packages and
cfs:filesystem packages in my meteor app. The problem is that I cant store my zip files in the FS.COllection. Here is some of the code :
//Defining the collection
Reports = new FS.Collection('reports',{
stores: [new FS.Store.FileSystem('reports', {path: "~/public"})]
});
//Trying to add a file to the collection
var zip = new JSZip();
Reports.insert(zip);
After running the code Im getting this error:
Error: DataMan constructor received data that it doesn't support
Is there any way to make those packages work with each other ?

The JSZip object is not a file by itself. You can generate a file from it with the generateAsync function. The file type you'll want to create depends on if you want this to run on the client or server and how you want to use this file. The file types supported by both libraries are: (as per documentation, I haven't tested all these myself)
Blob object (client only): { type: 'blob' }
Uint8Array: { type: 'uint8array' }
ArrayBuffer: { type: 'arraybuffer' }
Buffer object (server only): { type: 'nodebuffer' }
So for example this should work:
zip.generateAsync({ type: 'arraybuffer' })
.then(function (content) {
Reports.insert(content);
});

Related

Azure Bicep - can't access output from external module

I have a module called "privateendpoints.bicep" that creates a private endpoint as follows:
resource privateEndpoint_resource 'Microsoft.Network/privateEndpoints#2020-07-01' = {
name: privateEndpointName
location: resourceGroup().location
properties: {
subnet: {
id: '${vnet_resource.id}/subnets/${subnetName}'
}
privateLinkServiceConnections: [
{
name: privateEndpointName
properties: {
privateLinkServiceId: resourceId
groupIds: [
pvtEndpointGroupName_var
]
}
}
]
}
}
output privateEndpointIpAddress string = privateEndpoint_resource.properties.networkInterfaces[0].properties.ipConfigurations[0].properties.privateIPAddress
This is then referenced by a calling bicep file as follows:
module sqlPE '../../Azure.Modules/Microsoft.Network.PrivateEndpoints/1.0.0/privateendpoints.bicep' = {
name:'sqlPE'
params:{
privateEndpointName:'pe-utrngen-sql-${env}-001'
resourceId:sqlDeploy.outputs.sqlServerId
serviceType:'sql'
subnetName:'sub-${env}-utrngenerator01'
vnetName:'vnet-${env}-uksouth'
vnetResourceGroup:'rg-net-${env}-001'
}
}
var sqlPrivateLinkIpAddress = sqlPE.outputs.privateEndpointIpAddress
My problem is, it won't build. In VSCode I get the error The type "outputs" does not contain property "privateEndpointIpAddress"
This is the property I just added. Prior to me adding then all worked ok. I've made sure to save the updated external module and I've ensure right-clicked it in VSCode and selected build, it build ok and created a json file.
So, it seems the client bicep file is not picking up changes in the external module.
Any suggestions please?
The problem seemed to be caused by the fact I had the external module open in a separate VS Code instance. Once I closed this and opened the file in the same instance as the calling bicep file then it worked ok.

How to prevent loading Sharp module on Meteor client?

I used npm pkg Sharp on server's picture collection to transform imgs. The server code is like this:
import * as sharp from 'sharp';
export const Pictures = new Mongo.Collection('pictures');
export const PicturesStore = new UploadFS.store.GridFS({
collection: Pictures,
name: 'pictures',
filter: new UploadFS.Filter({
contentTypes: [ 'image/*' ],
}),
transformWrite(from, to, fileId, file) {
const transform = sharp().resize(300, 300).min().crop().toFormat('jpeg', { quality });
from.pipe(transform).pipe(to);
},
})
However on the client, it reports error:
cannot load native .node modules on the client.
The client doesn't run sharp functions actually. It only refers to PicturesStore and also create a minimongo collection for Pictures.
In another project, it uses webpack on the client. It can be configured to resolve sharp with an empty dummy object.
But how to create an empty dummy Sharp object to prevent loading Sharp module on Meteor client without webpack?
It turns out you have to write a Meteor package to define different files loaded on client and server. In you package.js, it's like this:
Package.onUse(function (api) {
api.mainModule('sharp-client.js', 'client');
api.mainModule('sharp-server.js', 'server');
});
In sharp-client.js, it's like this:
export var Sharp = {};
In sharp-server.js, it's like this:
import {
checkNpmVersions
} from 'meteor/tmeasday:check-npm-versions';
checkNpmVersions({
'sharp': '^0.20.5'
}, 'my:awesome-package');
export var Sharp = require('sharp');
done.

Download a file from a URL to a user accessible location

I am building an app using Nativescript/Angular 2
I want to be able to download a file from a URL and save it to the device in a location the average user would have no problems finding it. I believe downloads would be the best place for this on both iOS and Android. Please correct me if I am wrong.
The file can be any file type, not just an image. So mainly spreadsheet, word document, pdf, png, jpg, etc.
I have searched online and through the documentation. The documentation describes a method called getFile which gets a file and saves it to your device.
I have implemented this in my code as follows:
download (id) {
console.log('Download Started');
getFile("https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png").then(function (r) {
console.log(r.path);
}, function (e) {
//// Argument (e) is Error!
});
}
The problem with this is that it saves it to a non-user accessible location such as:
/data/user/0/com.myapp.example/files/logo.png
Update:
I have also tried specifying the path directly with:
fs.knownFolders.documents();
However, this method gets the documents folder for the current application that is NOT accessible by the user or external applications
After some unsuccessful attempts, I finally found how to save file to user "Downloads" folder (something like sdcard/Download). You can use android.os.Environment method to get this folder.
Add this in your component:
import { getFile } from 'tns-core-modules/http';
import * as fileSystem from "tns-core-modules/file-system";
import { isAndroid } from "tns-core-modules/platform";
import { alert } from "tns-core-modules/ui/dialogs";
declare var android;
<...>
public download (url, fileName) {
if (isAndroid) {
const permissions = require("nativescript-permissions");
permissions.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, "I need these permissions because I'm cool")
.then(() => {
let downloadedFilePath = fileSystem.path.join(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), fileName);
getFile(url, downloadedFilePath).then(resultFile => {
alert({
title: 'Saved!',
okButtonText: 'OK',
message: `File saved here:\n${resultFile.path}`
});
}, error => {
alert({
title: 'Error',
okButtonText: 'OK',
message: `${error}`
});
});
});
}
}
What else you should know:
1) There is no any kind of download indicator, standard system download bar also not appears, and I don't know how to solve this.
2) For iOS you may try to use
const filePath = fileSystem.path.join(fileSystem.knownFolders.ios.downloads().path, fileName);
getFile(url, filePath).then((resultFile) => {}, (error) => {});
I think, it's the shame that NS docs don't talk straight, that you can't save files in user accessible location only with NS functions. I figured it out only when I read comments in file /node_modules/tns-core-modules/file-system/file-system.d.ts
Hope this helps you.
To get it working on iPhone, you can do the following (TypeScript):
import { knownFolders, path } from "tns-core-modules/file-system";
let destination = path.join(knownFolders.documents(), "file_name.txt");
// logic to save your file here ...
// the important thing is that you have to save your file in knownFolders.documents()
Then in Info.plist, you have to add the following permissions:
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
Now if you go to your iPhone's Files app > On My iPhone > Your App's Name, you should see the file there.
Basically, the Documents folder is a private folder inside your application's directory that only you can see. However, when you enable the two permissions above, it allows file sharing so that your user can access the folder and its contents.
The same documentation says that you can specify the file location like this:
download (id) {
console.log('Download Started');
var folder = fs.knownFolders.documents();
var file = fs.path.join(folder.path, "logo.png");
var url = "https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png"
getFile(url, file).then(function (r) {
console.log(r.path);
}, function (e) {
//// Argument (e) is Error!
});
}
disclaimer: never tried it myself, just read the docs ...
You can specify a filesystem path directly, like this:
var folder = fs.Folder.fromPath('/sdcard/Download');
Note that /sdcard/Download will only work on Android; you can replace it with whatever (publicly accessible) folder you want to save your data to.
There doesn't yet seem to be a cross-platform way to choose a folder path, so you'll have to work out something manually. See this GitHub thread for more.
I realize that this is an older thread, but perhaps this can help someone:
If you use currentApp(), instead of documents(), you can access the folder you need. For example:
var directories = fs.knownFolders.currentApp();
var folder = directories.getFolder('./nameofaccessiblefolder');
I know this thread is 3 years ago but in case you have the same issue, I hope this solution will save time for you.
I solved the same issue by adding android:requestLegacyExternalStorage="true" inside the AndroidManifest.xml file
follow the thread here

How to save a file -in a folder- using FileSystem and CollectionFS ? (yeah, really.)

I think I'm missing something. I have read a lot of posts/examples and I can't save images on my system (I work locally).
What is my goal ?
I'm trying to save a file submitted by the user in a folder (server-side). Does it sound easy ? Maybe.
What's the issue ?
Short answer : I can't figure out how to save the file in my folder.
Do you want more information ?
The story of a file upload
I have read that to use the path parameter like new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" }) , I have to declare my collection server-side. But when I call Avatars.insert() (Avatars is the name of my collection), it seems like it doesn't exists. This makes sense because this collection exists only on the server.
So I've tried to declare the collection both server-side and client-side (I've read some examples about that) and that works ! The file is correctly added to MongoDB, but my folder is still empty (I'm not sure but I think this is because Avatars.insert() is called client-side so the collection used is the client-side one, the one which cannot take path parameter).
But no problem ! I've created 2 Meteor methods (one client-side and one server-side) called "updateAvatarFile". With this "trick", I'm able to do Meteor.call("updateAvatarFile", field.files[0]), which calls both server-side and client-side methods. So I can do some UI stuff in the client-side one and upload the file in the other. But I can't pass the file as a parameter.
field.files[0] contains the file client-side but server-side it's an empty object. My question is : How can I upload a file ?
I can't do it client-side (because I can't use path parameter) but I can pass the file to the server. I'm sure that I'm missing something but I can't figure what.
Here is how I go :
// /client/views/templates/settings.js
Template.settings.events({
'submit #updateAvatar': function (e, template) {
e.preventDefault();
const field = document.getElementsByName('avatar')[0];
Meteor.call('updateAvatarFile', field.files[0]);
}
});
// /client/lib/clientMethods.js
Meteor.methods({
'updateAvatarFile': function (file) {
// blabla
}
});
// /server/lib/serverMethods.js
Meteor.methods({
'updateAvatarFile': function (file) {
Avatars.insert(file, function (err, fileObj) {
if (err) {
console.log(err);
} else {
console.log(fileObj);
}
});
}
});
// /server/collections/serverAvatarCollection.js
Avatars = new FS.Collection("avatars", {
stores: [
new FS.Store.FileSystem("original", { path: "/public/images/user/avatar" }),
new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" })
],
filter: {
maxSize: 1000000, //1Mo
allow: { contentTypes: ['image/*'] }
},
onInvalid: function (message) {
//throw new Meteor.Error(403, message);
}
});
// /client/collections/clientAvatarCollection.js
// (this one is actually in a comment block)
Avatars = new FS.Collection("avatars", {
stores: [
new FS.Store.FileSystem("original"),
new FS.Store.FileSystem("thumb")
],
filter: {
maxSize: 1000000, //1Mo
allow: { contentTypes: ['image/*'] }
},
onInvalid: function (message) {
alert(message);
}
});
I've also tried to insert the file with the client-side method but I've got the same result (the file is added to MongoDB but not saved into a folder).
Using different path values didn't work either.
EDIT : Or maybe I'm trying to use the wrong package ? To my mind, transform a picture to chunks and save them into MongoDB sounds really weird and bad. Do you have any adivces ?
EDIT 2 :
answer to Michel Floyd (sorry about that, the character limit is annoying).
First, thanks for your answer !
1. At the moment, I'm just trying Meteor so I have both autopublish and insecure installed. Not publishing/subscribing to my collection cannot cause an issue, is that right ?
2. Before your answer I've tried to set up a collection available for both server and client by putting my avatarCollection.js in /collections. I was thinking that path which doesn't contains server or client are automatically available for the two sides. So what is the difference between /collections and /lib ? (I know that all files in a "lib" folder are loaded first). Is it a bad practice to put collections in /collections ? Maybe should I create a /lib/collections folder ?
3. (the last point, sorry for the long comment) I've tried what you advised above but it doesn't seems to work (or I am doing something wrong, again ><). When I use Avatars.insert(), CollectionFS don't save the file on my local storage. I've also checked the root of my HDD in case CollectionFS interpreted / to be the root of my machine but it doesn't. In the other hand, CollectionFS have created 4 collections in MongoDB (cfs._tempstore.chunks, cfs.avatars.filerecord, cfs_gridfs._tempstore.chunks and cfs_gridfs._tempstore.files) - the gridfs is weird. I have GridFS installed but I use FileSystem -. Those tables are not empty. That's why I think CollectionFS split my file into chunks and save them in MongoDB.
You're generally on the right track. CollectionFS uses storage adapters to deal with actual file storage. You can put files on S3, gridFS, or your local file system as you're trying to do. Putting the file contents in Mongo directly is usually avoided.
Firstly, define your collection:
Avatars = new FS.Collection("avatars", {
stores: [
new FS.Store.FileSystem("original", { path: "/public/images/user/avatar" }),
new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" })
],
filter: {
maxSize: 1000000, //1Mo
allow: { contentTypes: ['image/*'] }
},
onInvalid: function (message) {
//throw new Meteor.Error(403, message);
}
});
in /lib! This will make it available to both the server and the client.
Secondly, make sure you publish your avatars collection from the server and subscribe to it from the client. I don't see any publish/subscribe code in your question. You need it.
Thirdly, if you just do:
Avatars.insert(...);
on the client with a file then CollectionFS then CollectionFS will take care of storing it for you. The thing is, it won't be instantly available. It can take a little while for the actual upload and storage to happen. You can look at fileObj.isUploaded for example to see if the file is ready.

Meteor/Iron-Router: how to define routes using data from settings.json

For the URL to which a route applies I have a part defined in settings.json, like this
baseUrl: '/private'
My settings are published and accessible through the collections 'Settings' (on the client). So I tried the following:
Meteor.subscribe('settings');
Deps.autorun(function () {
var settings = Settings.findOne():
if (settings) {
Router.map(function () {
this.route('project', {
path: settings.baseUrl + '/:projectId,
controller: 'ProjectController'
});
});
}
});
The problem is that during initialisation the data is not yet on the client available, so I have to wait until the data is present. So far this approach doesn't work (yet). But before spending many hours I was wondering if someone has done this before or can tell me if this is the right approach ?
Updated answer:
I published solution in repository : https://github.com/parhelium/meteor-so-inject-data-to-html
. Test it by opening url : localhost:3000/test
In this case FastRender package is useless as it injects collection data in the end of head tag -> line 63.
Inject-Initial package injects data in the beginning of head tag -> line 106.
Needed packages:
mrt add iron-router
mrt add inject-initial
Source code:
Settings = new Meteor.Collection("settings");
if (Meteor.isClient) {
var settings = Injected.obj('settings');
console.log(settings);
Router.map(function () {
this.route('postShow', {
path: '/'+settings.path,
action: function () {
console.log("dynamic route !");
}
});
});
}
if (Meteor.isServer){
if(Settings.find().count() == 0){
Settings.insert({path:"test",data:"null"});
}
Inject.obj('settings', Settings.findOne());
}
Read about security in the bottom of the page : https://github.com/gadicc/meteor-inject-initial/
OLD ANSWER :
Below solution won't work in this specific case as FastRender injects data in the end of head tag. Because of that Routes are being initialized before injected data is present.
It will work when data from Settings collection will be sent together with html.
You can do that using package FastRender.
Create file server/router.js :
FastRender.onAllRoutes(function(path) {
// don't subscribe if client is downloading resources
if(/(css|js|html|map)/.test(path)) {
return;
}
this.subscribe('settings');
});
Create also publish function:
Meteor.publish('settings', function () {
return Settings.find({});
});
The above code means that if user open any url of your app then client will subscribe to "settings" publication and data will be injected on the server into html and available for client immediately.
I use this approach to be able to connect many different domains to meteor app and accordingly sent proper data.

Resources