How to show uploaded image in Keystonejs back-end - node.js

Very similar to problem here but I'm not using S3 files and the info in that link is somewhat dated (hasn't been updated since github issues linked from question above were closed).
My question is about how to get a preview of an uploaded image in Keystonejs's admin back-end. Although it seems like it's a hacky fix (editing keystone files as suggested in link above) I'm wondering if there's other options.
Although they've added support for S3 files and Types.CloudinaryImage is supported I can't get a preview of the uploaded image when it's just an uploaded image since Keystone treats it as an arbitrary file (not an image).
Screenshot: as you can see Keystone just shows the filename (highlighted in red).
Model is set up as follows:
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* Image Upload Model
* ==================
* A database model for uploading images to the local file system
*/
var ImageUpload = new keystone.List('ImageUpload');
var myStorage = new keystone.Storage({
adapter: keystone.Storage.Adapters.FS,
fs: {
path: keystone.expandPath('./public/uploads/images'),
publicPath: '/public/uploads/images',
}
});
ImageUpload.add({
name: { type: Types.Key, index: true },
image: {
type: Types.File,
storage: myStorage
},
createdTimeStamp: { type: String },
url: { type: String }
});
ImageUpload.defaultColumns = 'url, createdTimeStamp, image';
ImageUpload.register();

As far as I get it the only way - is to implement this by yourself.
It's not so scary as it my look but you should spend a lot of time to do that.
You need to investigate how for now different filed types are showed in admin page - for that you should take a look at template for admin page which is provided with KeystoneJS already (path: node_modules\keystone\admin\server\templates)
After that you might want to look for (path: node_modules\keystone\fields)
You might be interested in TYPES subfolder - cause there different field types rules
So your goal is to find corresponding field description (for your ImageUpload FileSystem model) or create a new one with img tag to show picture from url
I think File type is that what you are looking for - \node_modules\keystone\fields\types\file

Image previews are now possible in the latest master branch of keystone (see https://github.com/keystonejs/keystone/pull/4509). At the moment you need to depend on the git version of keystone, so put this in your package.json and run npm install:
"keystone": "https://github.com/keystonejs/keystone.git"
In your model, specify thumb: true on the image field in question. You also need the url property in the schema. For example:
const storage = new keystone.Storage({
adapter: keystone.Storage.Adapters.FS,
fs: {
path: keystone.expandPath('./uploads/images'),
publicPath: '/images/'
},
schema: {
url: true,
}
})
ImageUpload.add({
name: { type: Types.Key, index: true },
image: {
type: Types.File,
storage: myStorage,
thumb: true
},
createdTimeStamp: { type: String }
});
The admin UI should now show a small preview of the image and a link to it.

Related

Can't figure out how to upload file and json in form data react

I'm building a MERN stack web app and I want the users to be able to upload post with an image and post data.
The data looks like this:
title: 'The war in Ukraine continues',
text: 'Volodymyr Horbenko is the latest official to lose his job after Mr Zelensky said bosses failed to root out pro-Russian elements in the agency',
sources: [
'https://www.bbc.com/news/world-62223264',
'https://www.theguardian.com/world/live/2022/jul/19/russia-ukraine-war-live-news-putin-and-erdogan-to-meet-us-weaponry-stabilising-frontlines-ukraine-military-chief-says',
],
locations: [
{
description: 'boutcha',
type: 'Point',
coordinates: [-122.479887, 38.510312],
},
{
description: 'kyiv',
type: 'Point',
coordinates: [-122.582948, 38.585707],
},
],
};
now I also want to upload a picture this is just a photo that comes from an input.
I know that I should use formData to send the picture and i've tried uploading the data as well like this:
const form = new FormData();
form.append('data', JSON.stringify(data));
form.append('imageCover', fileInputRef.current.files[0]);
axios.post('/api/v1/stories/', form);
but when it comes to the backend I haven't been able to figure out how to retrieve the data into the request body. I've tried using multer app.use(multer.single('data'));(which works fine for the pictures) and app.use(express.json({ type: 'multipart/form-data' }));
but I get Error: Unexpected end of form, and Error:Unexpected token - in JSON at position 0 errors respectively.
also, if there is another way of sending both form data and files i'd like to hear about it, keep in mind that because there are nested objects I can't simply write form.append(key,value) for each pair in the data
app.post("/api/v1/stories", multer().single("imageCover"), function(req, res) {
var file = req.file;
var data = JSON.parse(req.body.data);
});

Gatsby `S3Object` that doesn't exist in the schema. Use `createTypes` to add the type before adding resolvers

Im trying to pull remote images from my S3 Bucket in Gatsby with the below code. I have a schema.graphql that builds a classic S3Object. But Gatsby Node throws the below error. I've been in the docs for days in this issue, can anyone point me in the right direction? I just need to get the image into the data layer of Gatsby so I can use Gatsby-image.
I have a feeling I need to update the S3Object to extend the Node Interface, i'm working on this now.
Error:
warn `createResolvers` passed resolvers for type `S3Object` that doesn't exist in the schema. Use `createTypes` to add the type before adding resolvers.
schema.graphql
type PortfolioItem #model #key(fields: ["title", "createdAt"]) {
id: ID!
title: String!
file: S3Object
thumbnailUrl: String
...
}
type S3Object {
bucket: String!
key: String!
region: String!
}
gatsby-node.js
exports.createResolvers = ({ actions, cache, createNodeId, createResolvers, store, reporter }) => {
const { createNode } = actions;
createResolvers({
S3Object: {
imageFile: {
type: `File`,
resolve(source, args, context, info) {
return createRemoteFileNode({
url: 'https://my-aws-bucket-url.s3.us-east-2.amazonaws.com/' + source.key,
store,
cache,
createNode,
createNodeId,
reporter,
});
},
},
},
});
};
Ok guys,
After literal weeks of investigating this. This is the answer, I know this will help dozens of people out there dealing with Gatsby and remote Images.
The entire goal here, was to have direct access to a remote image on a field on a Model in graphql DIRECTlY. No looping over long arrays of images from some Gatsby listAllImages query and crap like that. This adds an actual USEABLE GATSBY-IMAGE directly onto a field of your model to be used instantly.
Define an S3Object in your Schema.Graphql like below.
type S3Object {
bucket: String!
key: String!
region: String!
}
The answer here is creating a custom resolver in your gatsby node file. And the trick here, is going into the gatsby level schema and seeing the REAL name of your model. This is the secret. You need to run:
gatsby repl
This will put you into Gatsby's CLI and then enter:
schema
THIS WILL SHOW YOU THE COMPILED SCHEMA LEVEL NAMES. So your S3Object is actually called <your_api_name>_s3object. Using this name, that aparently had your api name prepended to it....smfh....is to be used with a custom resolver WITH a gql query inside of it to add a file to your model. See below. Now, where ever you add S3Object as a field type, you will have access to the image with an image-sharp query! You must also use gatsby-s3-image-source to grab all the images to be used into your files system!
This was a super complicated solve for a noob like me to Gatsby, I hope this solves someone elses remote file issue like mine!
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
ryanthedev_S3Object: {
imageFile: {
type: 'File', // this needs to be 'File', since it's not returning an 'ImageSharp' node anymore
resolve: (source, args, context, info) => {
// A promise is expected to be returned anyway so don't need
// to `await` the result here, if all we're doing is returning
// the `File` node
return context.nodeModel.runQuery({
query: {
filter: {
base: { eq: source.key },
},
},
type: 'File',
firstOnly: true,
});
},
},
},
};
createResolvers(resolvers);
};

Apostrophecms: Allow to upload PDF file only

I am having one field with relation. I want to upload only PDF files. Whereas I don't want to change the default setting for the dgad-attachments from app.js/default.js that allows all office type of files as those are needed in other places in project.
{
name: '_file',
type: 'joinByOne',
withType: 'apostrophe-file',
label: 'File',
required: true,
idField: 'fileId',
filters: {
projection: {
slug: 1,
title: 1,
attachment: 1
}
},
extensions: ['pdf'],
extensionMaps: {},
image: false
}
Can anyone help me on this please?
It sounds like you'll want to create a new file group in Apostrophe-attachments. You can use a file group to specify what types of files and/or extensions should be available when you add an apostrophe-attachments field to an object's schema. In order to add a file group that only contains PDFs, you will want to add this to the modules object in your app.js file:
'apostrophe-attachments': {
fileGroups: [
{
name: 'pdf',
label: 'Pdf',
extensions: ['pdf'],
image: false
}
]
}
This will create a new file group that will only allow files with the extension 'pdf'. Apostrophe-files isn't a regular schema type (it can't be added to an object's schema like other objects can). Instead of using apostrophe-files, it would be better to use apostrophe-attachments, which can be given a file group to restrict what types of files are allowed. In order to specify that group in your attachment, your new field will end up looking like this:
{
name: 'file',
type: 'attachment',
label: 'File',
group: 'pdf',
required: true
}
If you do decide you need to use a join directly to apostrophe-files, you will probably need to add some custom code in order to restrict the file type. If that is the case, you can find more information about apostrophe-files here:
https://apostrophecms.org/docs/modules/apostrophe-files/
You will probably be able to look at the way apostrophe-attachments handles file groups and replicate the behavior if necessary:
https://apostrophecms.org/docs/modules/apostrophe-attachments/

KeystoneJS: site-wide configuration variables in database

When creating a site with KeystoneJS, how might I add some site-wide configuration variables that are stored in the database - that can preferably be manipulated via the admin - in the vein of Craft CMS's 'globals'?
I can't find anything in the Keystone database documentation about this, and would prefer not to use a singleton with a Keystone list (e.g. by implementing a list that has only one item) if at all possible.
I've just had a chat with one of the Keystone developers about this. It's been widely discussed on ProductPains, and as it turns out, having a singleton with a list is currently (as of 0.3.x) the only way to do this:
Define a new model in e.g. models/Configuration.js:
const keystone = require('keystone');
const Types = keystone.Field.Types;
const Configuration = new keystone.List('Configuration', {
nocreate: true,
nodelete: true,
label: 'Configuration',
path: 'configuration',
});
Configuration.add({
siteName: { type: String },
siteDescription: { type: Types.Textarea },
});
Configuration.defaultColumns = 'siteName, siteDescription';
Configuration.register();
Add an update e.g. updates/0.0.2-configuration.js:
exports.create = {
Configuration: [
{ 'siteName': 'My site', 'siteDescription': 'TODO' }
]
};

Mongoose Changing Schema Format

We're rapidly developing an application that's using Mongoose, and our schema's are changing often. I can't seem to figure out the proper way to update a schema for existing documents, without blowing them away and completely re-recreating them from scratch.
I came across http://mongoosejs.com/docs/api.html#schema_Schema-add, which looks to be right. There's little to no documentation on how to actually implement this, making it very hard for someone who is new to MongoDB.
I simply want to add a new field called enabled. My schema definition is:
var sweepstakesSchema = new Schema({
client_id: {
type: Schema.Types.ObjectId,
ref: 'Client',
index: true
},
name: {
type: String,
default: 'Sweepstakes',
},
design: {
images: {
type: [],
default: []
},
elements: {
type: [],
default: []
}
},
enabled: {
type: Boolean,
default: false
},
schedule: {
start: {
type: Date,
default: Date.now
},
end: {
type: Date,
default: Date.now
}
},
submissions: {
type: Number,
default: 0
}
});
Considering your Mongoose model name as sweepstakesModel,
this code would add enabled field with boolean value false to all the pre-existing documents in your collection:
db.sweepstakesModel.find( { enabled : { $exists : false } } ).forEach(
function (doc) {
doc.enabled = false;
db.sweepstakesModel.save(doc);
}
)
There's nothing built into Mongoose regarding migrating existing documents to comply with a schema change. You need to do that in your own code, as needed. In a case like the new enabled field, it's probably cleanest to write your code so that it treats a missing enabled field as if it was set to false so you don't have to touch the existing docs.
As far as the schema change itself, you just update your Schema definition as you've shown, but changes like new fields with default values will only affect new documents going forward.
I was also searching for something like migrations, but didn't find it. As an alternative you could use defaults. If a key has a default and the key doesn't exist, it will use the default.
Mongoose Defaults
Default values are applied when the document skeleton is constructed. This means that if you create a new document (new MyModel) or if you find an existing document (MyModel.findById), both will have defaults provided that a certain key is missing.
I had the exact same issue, and found that using findOneAndUpdate() rather than calling save allowed us to update the schema file, without having to delete all the old documents first.
I can post a code snippet if requested.
You might use mongo shell to update the existing documents in a specific collection
db.SweeptakesModel.update({}, {$set: {"enabled": false}}, {upsert:false, multi:true})
I had a similar requirement of having to add to an existing schema when building an app with Node, and only found this (long ago posted) query to help.
The schema I added to by introducing the line in the original description of the schema and then running something similar to the following line, just the once, to update existing records:
myModelObject.updateMany( { enabled : { $exists : false } }, { enabled : false } )
'updateMany' being the function I wanted to mention here.
just addition to what Vickar was suggesting, here Mongoose Example written on Javascript (Nodejs):
const mongoose = require('mongoose');
const SweeptakesModel = mongoose.model(Constants.SWEEPTAKES,sweepstakesSchema);
SweeptakesModel.find( { enabled : { $exists : false } }).then(
function(doc){
doc.enabled = false;
doc.save();
}
)

Resources