How to delete child object in Strapi - javascript

I used this topic to delete upload related to articles and it work :
How to delete file from upload folder in strapi?
Now i want a cascade deletion. For when i delete a project, articles and upload will deleted also.
Somebody know how to delete a project which will delete an article & upload file ?
This code work to delete a project and children but not the file. And if i try to delete just an article, the controller is called and the file is deleted.
/api/projects/controllers/projects.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const project = await strapi.services.projects.delete({ id });
if (project){
for (let article of project.articles) {
strapi.services.articles.delete({ 'id' : article._id } );
}
}
return sanitizeEntity(project, { model: strapi.models.projects });
}
/api/articles/services/articles.js
delete(params) {
return strapi.query('articles').delete(params);
}
/api/articles/controllers/article.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.articles.delete({ id });
if (entity) {
strapi.plugins.upload.services.upload.remove(entity.articleFile);
}
return sanitizeEntity(entity, { model: strapi.models.articles });
}
}
The controller is called when i used the url in API like that :
DELETE http://localhost:port/banners/<project_ID>
The strapi.services.xxx.delete is called in the code

Actually you remove articles files only when you pass into your article controller. But in project controller context, you never remove files.
To fix it, remove article files into the article service.
/api/articles/services/articles.js
async delete(params) {
const entity = await strapi.query('articles').delete(params);
if (entity) {
await strapi.plugins.upload.services.upload.remove(entity.articleFile);
}
return entity;
}
/api/articles/controllers/article.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.articles.delete({ id });
// here articles files have been deleted by services.articles.delete
return sanitizeEntity(entity, { model: strapi.models.articles });
}
}
According to strapi concepts :
Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify controllers logic.
https://strapi.io/documentation/v3.x/concepts/services.html#concept
Controllers are JavaScript files which contain a set of methods called actions reached by the client according to the requested route. It means that every time a client requests the route, the action performs the business logic coded and sends back the response. They represent the C in the MVC pattern. In most cases, the controllers will contain the bulk of a project's business logic.
https://strapi.io/documentation/v3.x/concepts/controllers.htm

Related

How to create custom Registration and Login API using Strapi?

I am using strapi to create APIs.
I want to implement my own Registration API and Login API.
I checked the documentation of strapi but i am not finding any custom API for this.
can any one help me on this?
Same answer, but in more detail:
Strapi creates an Auth controller automatically for you and you can overwrite its behavior.
Copy the function(s) you need (e.g. register) from this file:
node_modules/strapi-plugin-users-permissions/controllers/Auth.js
to:
your_project_root/extensions/users-permissions/controllers/Auth.js
Now you can overwrite the behavior, e.g. pass a custom field inside the registration process {"myCustomField": "hello world"} and log it to the console:
async register(ctx) {
...
...
// log the custom field
console.log(params.myCustomField)
// do something with it, e.g. check whether the value already exists
// in another content type
const itExists = await strapi.query('some-content-type').findOne({
fieldName: params.myCustomField
});
if (!itExists) {
return ctx.badRequest(...)
} else {
console.log('check success')
}
}
Actually, strapi creates an Auth controller to handle these requests. You can just change them to fit in your need.
The path to the controller is:
plugins/users-permissions/controllers/Auth.js
in order to create custom users-permissons apis on server side you have to create
src/extensions/users-permissions/strapi-server.js
and in that file can write or override existing user-permissions plugin apis
here is the example for users/me
const _ = require('lodash');
module.exports = (plugin) => {
const getController = name => {
return strapi.plugins['users-permissions'].controller(name);
};
// Create the new controller
plugin.controllers.user.me = async (ctx) => {
const user = ctx.state.user;
// User has to be logged in to update themselves
if (!user) {
return ctx.unauthorized();
}
console.log('calling about meeeeeeeeeee------')
return;
};
// Add the custom route
plugin.routes['content-api'].routes.unshift({
method: 'GET',
path: '/users/me',
handler: 'user.me',
config: {
prefix: '',
}
});
return plugin;
};

Meteor Files Storing a image url in Mongo collection

I'm really lost when it comes to file uploading in meteor and manage the data between client and server.
I'm using Meteor Files from Veliov Group to upload multiple images on the client side. They're getting stored in a FilesCollection called Images and I have my Mongo.Collection called Adverts.
collections.js:
Adverts = new Mongo.Collection('adverts');
Images = new FilesCollection({
collectionName: 'Images',
storagePath: () => {
return `~/public/uploads/`;
},
allowClientCode: true, // Required to let you remove uploaded file
onBeforeUpload(file) {
// Allow upload files under 10MB, and only in png/jpg/jpeg formats
if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.ext)) {
return true;
} else {
return 'Limit 10mb';
}
}
});
// if client subscribe images
if (Meteor.isClient) {
Meteor.subscribe('files.images.all');
};
// if server publish images
if (Meteor.isServer) {
Images.allowClient();
Meteor.publish('files.images.all', () => {
return Images.collection.find();
});
};
What I'm trying to achieve is, when I upload the images, I wanna store the URLs on the document in Adverts that I'm working with (I'm using iron:router to access those documents _id).
I managed to get the URL but only for the first image uploaded, my code for what I saw on the docs:
Template.imageUpload.helpers({
imageFile: function () {
return Images.collection.findOne();
},
myImage: () => {
console.log(Images.findOne({}).link())
}
})
Template.imageUpload.events({
'change #fileInput': function (e, template) {
if (e.currentTarget.files) {
_.each(e.currentTarget.files, function (file) {
Images.insert({
file: file
});
});
}
}
})
I was using a Meteor.Call to send the URL to the server, but I couldn't manage to update the document with a new property pic and the value url of the image
server.js:
imageUpload: (actDoc, imgURL) => { // actDoc is the document id that I'm working on the client
Adverts.update({'reference': actDoc}, {$set: {'pic': imgURL}})
},
This is probably a dumb question and everything might in the docs, but I've readed those docs back and forth and I can't manage to understand what I need to do.
The answer for my problem was to do it server side
main.js server
FSCollection.on('afterUpload'), function (fileRef) {
var url = 'http://localhost:3000/cdn/storage/images/' + fileRef._id + '/original/' + fileRef._id + fileRef.extensionWithDot;
}
MongoCollection.update({'_id': docId}, { $set: {url: imgUrl }}})

Meteor session is undefined after page redirect

I am making a game that requires a lobby of players, but no accounts. Kind of like the game, Spyfall. I am using Meteor Sessions to know which player joined the lobby so that I can return the proper data for that specific player. I have a join.js component where the user enters in the lobby access code and the user's name. This component also redirects the user to the lobby. Join.js is at the route, /join, and the lobbies are at the route, /:lobby. Here is the join.js handleSubmit method which takes the user input and puts it in the players collection:
handleSubmit(event) {
event.preventDefault();
var party = Players.findOne({code: this.refs.code.value});
if(typeof party !== 'undefined') {
Meteor.call('players.insert', this.refs.code.value, this.refs.name.value);
var playerId = Players.findOne({"name": this.refs.name.value})._id;
Meteor.call('players.current', playerId);
location.href = "/" + this.refs.code.value;
} else {
document.getElementById("error").innerHTML = 'Please enter a valid party code';
}
I am using Sessions in the Meteor.methods in the players.js collection to get the current user.
import { Mongo } from 'meteor/mongo';
import { Session } from 'meteor/session';
Meteor.methods({
'players.insert': function(code, name) {
console.log('adding player: ', name , code);
Players.insert({code: code, name: name});
},
'players.updateAll': function(ids, characters, banners, countries, ancestors) {
for (var i = 0; i < characters.length; i++){
Players.update({_id: ids[i]}, {$set: {character: characters[i], banner: banners[i], country: countries[i], ancestor: ancestors[i]},});
}
},
'players.current': function(playerId) {
Session.set("currentPlayer", playerId);
console.log(Session.get("currentPlayer"));
},
'players.getCurrent': function() {
return Session.get("currentPlayer");
}
});
export const Players = new Mongo.Collection('players');
The console.log in the 'players.current' method returns the proper player id, but once the page redirects to /:lobby, the players.getCurrent returns undefined. I want players.getCurrent to return the same value that the console.log returns. How do I fix this issue? This is the function to get the current player id in the lobby.js:
getCurrentPlayerId() {
return Meteor.call('players.getCurrent');
}
Per the Meteor API, Meteor methods are meant to be the way you define server side behavior that you call from the client. They are really intended to be defined on the server.
Methods are remote functions that Meteor clients can invoke with Meteor.call.
A Meteor method defined on the client simply acts as a stub.
Calling methods on the client defines stub functions associated with server methods of the same name
Based on your code it looks like you are doing everything client side. In fact, session is part of the Meteor client API (can't use on the server).
Session provides a global object on the client that you can use to store an arbitrary set of key-value pairs.
Therefore, If I were you, I would just implement all this logic in some sort of util file that you can then import into the Templates where you need it. You are effectively doing the same thing, you just need to use regular functions instead of Meteor methods.
Here is an example util file (be sure to update the Players import based upon your project's file structure).
import { Players } from './players.js';
import { Session } from 'meteor/session';
export const players = {
insert: function(code, name) {
console.log('adding player: ', name , code);
return Players.insert({code: code, name: name});
},
updateAll: function(ids, characters, banners, countries, ancestors) {
for (var i = 0; i < characters.length; i++) {
Players.update({_id: ids[i]}, {$set: {character: characters[i], banner: banners[i], country: countries[i], ancestor: ancestors[i]},});
}
},
setCurrent: function(playerId) {
Session.set("currentPlayer", playerId);
console.log(Session.get("currentPlayer"));
},
getCurrent: function(unixTimestamp) {
return Session.get("currentPlayer");
},
};
Then, you can import this into whatever template you have that has defined the event handler you included in your question.
import { Template } from 'meteor/templating';
import { players } from './utils.js';
Template.template_name.events({
'click .class': handleSubmit (event, instance) {
event.preventDefault();
var party = Players.findOne({code: this.refs.code.value});
if (typeof party !== 'undefined') {
var playerId = players.insert(this.refs.code.value, this.refs.name.value);
players.setCurrent(playerId);
location.href = "/" + this.refs.code.value;
} else {
document.getElementById("error").innerHTML = 'Please enter a valid party code';
}
},
});
Of course you will need to modify the above code to use your correct template name and location of the utils file.
I think the issue is that you are using
location.href = "/" + this.refs.code.value;
instead of using
Router.go("/"+this.refs.code.value);
if using Iron Router. Doing this is as if you are refreshing the page. And here's a package to maintain Session variables across page refreshes.

How to write a simple chat, without a database on meteor.js and react.js?

I want to write a simple chat on meteor.js and thus I do not want to store the data in the database.
But I never found how to make an application without a database.
Here is an example of code as I can imagine.
Server code:
export let ws = [{_id:'1', text:'test1'}, {_id:'2', text:'test2'}];
Meteor.publish('ws', function wsPub() { return ws; });
let ctr = 3;
Meteor.methods({
'addMsg'(text) { ws.push({_id:ctr+1, text:text}); }
});
and client code:
import {ws} from '../api/model.js';
class Rtc extends Component {
constructor(props) {
super(props);
}
addMsg(e){
e.preventDefault();
Meteor.call('addMsg', this.refs.input.value);
}
render() {
return (
<div>
{this.props.ws.map((item, i)=>{
return(<span key={i._id}>{item.text}</span>);
})}
<input type="text" ref="input" />
<input type="submit" value="submit" onClick={this.addMsg.bind(this)}/>
</div>
);
}
}
export default createContainer( () => {
Meteor.subscribe('ws');
return { ws: ws };
}, Rtc);
but I do not understand what I wrote is not so in the createContainer?
UPD: I updated server code, but still websockets does not work:
Meteor.publish('ws', function wsPub() {
let self = this;
ws.forEach( (msg)=> {
self.added( "msg", msg._id, msg.text );
});
self.ready();
// return ws;
});
If you want to control what is sent over a publish, get a reference to the "publish instance" (really a specific client with a specific subscription) and use its add/change/remove commands:
let messages = [];
let clients = [];
Meteor.publish('ws', function() {
clients.push(this);
_.each(messages, (message) => {this.added('msg', message._id, message);});
this.ready();
});
Meteor.methods({
addMsg(text) {
let newMessage = {_id: Meteor.uuid(), text: text};
messages.push(newMessage);
_.each(clients, (client) => {client.added('msg', newMessage._id, newMessage);});
}
});
Regarding your code that you wrote in an update: you're sending a string where the function added expects a document (an object). Also, unlike this example above, you do not tell the clients when the ws (messages array) has changed.
I'd recommend also renaming these things to be more verbose and clear :)
What you suppose won't work. Because Meteor.publish returns a cursor to a Collection or array of Collections. According to the official documentation:
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor’s documents to each subscribed client. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
Again, when you subscribe to a publication, it stores the data(as cursor to the same collection as the publication) locally in MiniMongo. So a chat without database is not technically possible with pub-sub in Meteor.

Getting x from remote sources and mirroring on to a list

Currently I have this, if with the full app it will create a post with my chosen parameters, however I am very new with vue.js, My aim is to be able to have a text file of such (or other way of storing (json etc)) the values, and then having the js script iterate through the file and display as cards, so for example in the file I would have
"Mark", "http://google.com", "5556", "image"
Or of course using json or similar, I'm up to what ever but my problem is, I don't know how to get values from a remote source and mirror it on to the document, can anyone help?, for clarity here's the snippet of code that I'm using
var app = new Vue({
el: '#app',
data: {
keyword: '',
postList: [
new Post(
'Name',
'Link',
'UID',
'Image'),
]
},
});
-- EDIT --
I'd like to thank the user Justin MacArthur for his quick answer, if you or anyone else doesn't mind answering another one of my painfully incompetent questions. This is the function that adds the cards in a nutshell
var Post = function Post(title, link, author, img) {
_classCallCheck(this, Post);
this.title = title;
this.link = link;
this.author = author;
this.img = img;
};
I can now get the data from the text file, meaning I could do, and assuming I have response defined (that being the http request) it'll output the contents of the file, how would I do this for multiple cards- as, as one would guess having a new URL for each variable in each set of four in each card is not just tedious but very inefficient.
new Post(
response.data,
)
The solution you're looking for is any of the AJAX libraries available. Vue used to promote vue-resource though it recently retired that support in favor of Axios
You can follow the instructions on the github page to install it in your app and the usage is very simple.
// Perform a Get on a file/route
axios.get(
'url.to.resource/path',
{
params: {
ID: 12345
}
}
).then(
// Successful response received
function (response) {
console.log(response);
}
).catch(
// Error returned by the server
function (error) {
console.log(error);
}
);
// Perform a Post on a file/route
// Posts don't need the 'params' object as the second argument is sent as the request body
axios.post(
'url.to.resource/path',
{
ID: 12345
}
).then(
// Successful response received
function (response) {
console.log(response);
}
).catch(
// Error returned by the server
function (error) {
console.log(error);
}
);
Obviously in the catch handler you'd have your error handing code, either an alert or message appearing on the page. In the success you could have something along the lines of this.postList.push(new Post(response.data.name, response.data.link, response.data.uid, response.data.image));
To make it even easier you can assign axios to the vue prototype like this:
Vue.prototype.$http = axios
and make use of it using the local vm instance
this.$http.post("url", { data }).then(...);
EDIT:
For your multi-signature function edit it's best to use the arguments keyword. In Javascript the engine defines an arguments array containing the parameters passed to the function.
var Post = function Post(title, link, author, img) {
_classCallCheck(this, Post);
if(arguments.length == 1) {
this.title = title.title;
this.link = title.link;
this.author = title.author;
this.img = title.img;
} else {
this.title = title;
this.link = link;
this.author = author;
this.img = img;
}
};
Be careful not to mutate the arguments list as it's a reference list to the parameters themselves so you can overwrite your variables easily without knowing it.

Categories

Resources