Commit 82d423ee authored by Klaus-Dieter Quibeldey-Cirkel's avatar Klaus-Dieter Quibeldey-Cirkel 🏀

Merge branch '1176-refactor-card-set-subscriptions' into 'staging'

Resolve "Refactor Card set subscriptions"

Closes #1176 and #1423

See merge request arsnova/flashcards!1722
parents cadf45d1 ab7ba0b1
......@@ -35,7 +35,6 @@ tap:i18n-db
aldeed:collection2
okgrow:promise
themeteorchef:bert
easy:search
barbatus:stars-rating
rexzh0u:kramed
simple:katex
......
......@@ -46,9 +46,6 @@ diff-sequence@1.1.0
dsyko:jquery-ui-touch-punch@1.2.4
dynamic-import@0.5.0
east5th:package-scan@0.0.5
easy:search@2.2.1
easysearch:components@2.2.1
easysearch:core@2.2.0
ecmascript@0.12.1
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.8.0
......@@ -121,12 +118,6 @@ observe-sequence@1.0.16
okgrow:promise@0.9.5
ordered-dict@1.1.0
patrickml:braintree@1.32.0
peerlibrary:assert@0.2.5
peerlibrary:base-component@0.16.0
peerlibrary:blaze-components@0.21.0
peerlibrary:computed-field@0.9.0
peerlibrary:data-lookup@0.1.0
peerlibrary:reactive-field@0.3.0
percolate:synced-cron@1.3.2
pfafman:filesaver@1.3.2
polyroid:font-roboto@1.0.1
......
......@@ -20,7 +20,7 @@
@import '../imports/ui/learn/progress.scss';
@import '../imports/ui/pomodoroTimer/pomodoroTimer.scss';
@import '../imports/ui/admin/admin.scss';
@import '../imports/ui/access_denied/access_denied.scss';
@import '../imports/ui/accessDenied/accessDenied';
@import '../imports/ui/markdeep/content.scss';
@import '../imports/ui/main/sweetAlert2.scss';
@import '../imports/ui/loadingScreen/loadingScreen.scss';
......
......@@ -816,9 +816,24 @@ $themes: (
border-bottom-color: $search_results_separator !important;
}
.search-subject {
background-color: $search_results_subject_background !important;
color: $search_results_subject_text !important;
.search-subject-personal {
background-color: $private_background !important;
color: $private_text !important;
}
.search-subject-edu {
background-color: $edu_background !important;
color: $edu_text !important;
}
.search-subject-pro {
background-color: $pro_background !important;
color: $pro_text !important;
}
.search-subject-free {
background-color: $free_background !important;
color: $free_text !important;
}
.search-content {
......
......@@ -396,8 +396,6 @@
//notification colors
"notification_text_smartphones": white,
//search colors
"search_results_subject_background": $thm_green,
"search_results_subject_text": white,
"search_results_content_background": LightGoldenRodYellow,
"search_results_content_text": $thm_gray,
"search_results_author_background": $thm_gray,
......
......@@ -398,8 +398,6 @@
//notification colors
"notification_text_smartphones": white,
//search colors
"search_results_subject_background": $thm_green,
"search_results_subject_text": white,
"search_results_content_background": LightGoldenRodYellow,
"search_results_content_text": $thm_gray,
"search_results_author_background": $thm_gray,
......
......@@ -18,7 +18,7 @@ function getCardsetInfo(cardset) {
[TAPi18n.__('set-list.cardsetInfoStatic', {}, "de"), ""],
[TAPi18n.__('set-list.name', {}, "de"), cardset.name],
[TAPi18n.__('modal-dialog.kind', {}, "de"), cardset.kind],
[TAPi18n.__('cardset.info.rating', {}, "de"), cardset.relevance],
[TAPi18n.__('cardset.info.rating', {}, "de"), cardset.rating],
[TAPi18n.__('cardset.info.quantity', {}, "de"), cardset.quantity],
[TAPi18n.__('cardset.info.author', {}, "de"), getAuthorName(cardset.owner)],
[TAPi18n.__('cardset.info.release', {}, "de"), moment(cardset.date).locale("de").format('LL')],
......
......@@ -11,79 +11,81 @@ import {UserPermissions} from "./permissions";
export const Cardsets = new Mongo.Collection("cardsets");
export function getShuffledCardsetReferences(kind) {
let shuffleddCardsetReferences = [];
let shuffledCardsets = Cardsets.find({
shuffled: true,
visible: true,
kind: {$in: kind}
}, {fields: {cardGroups: 1}}).fetch();
for (let i = 0; i < shuffledCardsets.length; i++) {
for (let k = 0; k < shuffledCardsets[i].cardGroups.length; k++) {
shuffleddCardsetReferences.push(shuffledCardsets[i].cardGroups[k]);
}
}
return shuffleddCardsetReferences;
}
if (Meteor.isServer) {
Meteor.publish("cardsets", function () {
if (this.userId) {
if (Roles.userIsInRole(this.userId, [
'admin',
'editor'
])) {
return Cardsets.find({kind: {$nin: ['server']}});
} else if (Roles.userIsInRole(this.userId, 'lecturer')) {
return Cardsets.find(
{
$or: [
{visible: true},
{request: true},
{owner: this.userId},
{_id: {$in: getShuffledCardsetReferences(['free', 'edu', 'pro'])}}
]
});
} else if (this.userId && !Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
Meteor.publish("demoCardsets", function () {
return Cardsets.find({kind: {$in: ['demo']}});
});
Meteor.publish("makingOfCardsets", function () {
return Cardsets.find({kind: {$in: ['demo']}});
});
Meteor.publish("wordcloudCardsets", function () {
return Cardsets.find({wordcloud: true});
});
Meteor.publish("cardset", function (cardset_id) {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
let cardset = Cardsets.findOne({_id: cardset_id}, {fields: {_id: 1, kind: 1, owner: 1, cardGroups: 1}});
if (cardset.kind === 'personal') {
if (!UserPermissions.isOwner(cardset.owner) && !UserPermissions.isAdmin()) {
return 0;
}
}
return Cardsets.find({
$or: [
{_id: cardset._id},
{_id: {$in: cardset.cardGroups}}
]
});
}
});
Meteor.publish("allCardsets", function () {
if (this.userId && UserPermissions.isAdmin()) {
return Cardsets.find({shuffled: false});
}
});
Meteor.publish("workloadCardsets", function () {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
let workload = Workload.find({user_id: this.userId}, {fields: {cardset_id: 1}}).fetch();
let cardsets = [];
for (let i = 0; i < workload.length; i++) {
cardsets.push(workload[i].cardset_id);
}
return Cardsets.find({_id: {$in: cardsets}});
}
});
Meteor.publish("myCardsets", function () {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Cardsets.find({owner: this.userId, shuffled: false});
}
});
Meteor.publish("poolCardsets", function () {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Cardsets.find({kind: {$in: ['free', 'edu', 'pro']}, shuffled: false});
}
});
Meteor.publish("repetitoriumCardsets", function () {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Cardsets.find({kind: {$in: ['free', 'edu', 'pro']}, shuffled: true});
}
});
Meteor.publish("editShuffleCardsets", function (cardset_id) {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
if (UserPermissions.isAdmin()) {
return Cardsets.find({
$or: [
{_id: cardset_id},
{shuffled: false}
]
});
} else {
return Cardsets.find(
{
$or: [
{visible: true},
{owner: this.userId},
{_id: {$in: getShuffledCardsetReferences(['free', 'edu', 'pro'])}}
{_id: cardset_id},
{owner: this.userId, shuffled: false},
{kind: {$in: ['free', 'edu', 'pro']}, shuffled: false}
]
});
}
} else {
return Cardsets.find(
{
$or: [
{kind: {$in: ['demo']}},
{wordcloud: true}
]
},
{
fields:
{
_id: 1,
owner: 1,
name: 1,
quantity: 1,
kind: 1,
description: 1,
cardType: 1,
difficulty: 1,
date: 1,
dateUpdated: 1,
wordcloud: 1,
cardGroups: 1,
license: 1,
shuffled: 1,
learningActive: 1,
originalAuthorName: 1,
workload: 1
}
});
}
});
Meteor.publish("tags", function () {
......@@ -145,6 +147,11 @@ const CardsetsSchema = new SimpleSchema({
type: Boolean
},
relevance: {
type: Number,
decimal: true,
optional: true
},
rating: {
type: Number,
decimal: true
},
......@@ -228,37 +235,45 @@ const CardsetsSchema = new SimpleSchema({
Cardsets.attachSchema(CardsetsSchema);
CardsetsIndex = new EasySearch.Index({
collection: Cardsets,
fields: [
'name'
],
engine: new EasySearch.Minimongo({
selector: function (searchObject, options, aggregation) {
// Default selector
const defSelector = this.defaultConfiguration().selector(searchObject, options, aggregation);
Meteor.methods({
getSearchCategoriesResult: function (searchValue) {
if (!Meteor.userId() || !UserPermissions.isNotBlockedOrFirstLogin()) {
throw new Meteor.Error("not-authorized");
} else if (searchValue !== undefined && searchValue !== null && searchValue.length > 1) {
let query = {};
// Filter selector
const selector = {};
selector.$and = [
defSelector,
{
if (UserPermissions.isAdmin()) {
query.name = {$regex: searchValue, $options: "i"};
query.kind = {$nin: ['demo', 'server']};
} else {
query = {
name: {$regex: searchValue, $options: "i"},
$or: [
{
owner: Meteor.userId()
},
{
visible: true
}
{owner: Meteor.userId()},
{kind: {$nin: ['demo', 'server', 'personal']}}
]
};
}
let results = Cardsets.find(query, {fields: {_id: 1}}).fetch();
let filter = [];
if (results !== undefined) {
for (let i = 0; i < results.length; i++) {
filter.push(results[i]._id);
}
];
return selector;
return Cardsets.find({_id: {$in: filter}}, {
fields: {
_id: 1,
name: 1,
owner: 1,
description: 1,
kind: 1
}
}).fetch();
}
} else {
return [];
}
})
});
Meteor.methods({
},
/**
* Adds a cardset to the personal deck of cards.
* @param {String} name - Title of the cardset
......@@ -298,7 +313,7 @@ Meteor.methods({
if (cardType < 0) {
cardType = 0;
}
return Cardsets.insert({
let cardset = Cardsets.insert({
name: name.trim(),
description: description,
date: new Date(),
......@@ -312,7 +327,7 @@ Meteor.methods({
reviewed: false,
reviewer: 'undefined',
request: false,
relevance: 0,
rating: 0,
raterCount: 0,
quantity: quantity,
license: [],
......@@ -333,6 +348,8 @@ Meteor.methods({
difficulty: difficulty,
noDifficulty: !CardType.gotDifficultyLevel(cardType)
}, {trimStrings: false});
Meteor.call('updateCardsetCount', Meteor.userId());
return cardset;
},
/**
* Delete selected Cardset from database if user is auhorized.
......@@ -360,6 +377,7 @@ Meteor.methods({
Ratings.remove({
cardset_id: id
});
Meteor.call('updateCardsetCount', Meteor.userId());
} else {
throw new Meteor.Error("not-authorized");
}
......@@ -410,7 +428,10 @@ Meteor.methods({
learningActive: false
}
});
let users = Workload.find({cardset_id: cardset._id, 'leitner.bonus': true}, {fields: {user_id: 1}}).fetch();
let users = Workload.find({
cardset_id: cardset._id,
'leitner.bonus': true
}, {fields: {user_id: 1}}).fetch();
for (let i = 0; i < users.length; i++) {
Workload.update({
cardset_id: cardset._id,
......@@ -636,39 +657,6 @@ Meteor.methods({
}
}
},
updateRelevance: function (cardset_id) {
check(cardset_id, String);
let relevance = 0;
let ratings = Ratings.find({cardset_id: cardset_id});
let count = ratings.count();
if (count !== 0) {
let amount = 0;
ratings.forEach(function (rate) {
amount = amount + rate.rating;
});
let result = (amount / count).toFixed(2);
relevance = Number(result);
}
let kind = Cardsets.findOne(cardset_id).kind;
switch (kind) {
case 'free':
break;
case 'edu':
relevance += 0.1;
break;
case 'pro':
relevance += 0.2;
break;
default:
break;
}
return relevance;
},
publishCardset: function (id, kind, price, visible) {
check(id, String);
check(kind, String);
......@@ -677,17 +665,11 @@ Meteor.methods({
// Make sure only the task owner can make a task private
let cardset = Cardsets.findOne(id);
if (UserPermissions.isAdmin() || UserPermissions.isOwner(cardset.owner)) {
Meteor.call("updateRelevance", id, function (error, relevance) {
if (!error) {
Cardsets.update(id, {
$set: {
kind: kind,
price: price.toString().replace(",", "."),
visible: visible,
relevance: relevance,
raterCount: Number(Ratings.find({cardset_id: id}).count())
}
});
Cardsets.update(id, {
$set: {
kind: kind,
price: price.toString().replace(",", "."),
visible: visible
}
});
if (kind !== "personal") {
......
......@@ -174,7 +174,7 @@ Meteor.methods({
reviewed: false,
reviewer: 'undefined',
request: false,
relevance: 0,
rating: 0,
raterCount: 0,
quantity: data[0].quantity,
license: [],
......@@ -198,7 +198,7 @@ Meteor.methods({
}, {trimStrings: false});
if (cardset_id) {
data.shift();
Meteor.call('updateLearnerCount', cardset_id);
Meteor.call('updateCardsetCount', Meteor.userId());
return importCards(data, Cardsets.findOne(cardset_id), 0);
} else {
return false;
......@@ -289,7 +289,7 @@ Meteor.methods({
reviewed: false,
reviewer: 'undefined',
request: false,
relevance: 0,
rating: 0,
raterCount: 0,
quantity: cardset[0].quantity,
license: [],
......@@ -332,7 +332,7 @@ Meteor.methods({
reviewed: false,
reviewer: 'undefined',
request: false,
relevance: 0,
rating: 0,
raterCount: 0,
quantity: totalQuantity,
license: [],
......
import {Meteor} from "meteor/meteor";
import {Mongo} from "meteor/mongo";
import {Cardsets} from "./cardsets.js";
import {check} from "meteor/check";
import {UserPermissions} from "./permissions";
export const Paid = new Mongo.Collection("paid");
if (Meteor.isServer) {
Meteor.publish("paid", function () {
if (this.userId && !Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
return Paid.find({
$or: [
{user_id: this.userId},
{
cardset_id: {
$in: Cardsets.find({owner: this.userId}).map(function (doc) {
return doc._id;
})
}
}
]
});
Meteor.publish("paidCardset", function (cardset_id) {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Paid.find({user_id: this.userId, cardset_id: cardset_id});
}
});
Meteor.publish("paidCardsets", function () {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Paid.find({user_id: this.userId});
}
});
}
......
......@@ -2,19 +2,23 @@ import {Meteor} from "meteor/meteor";
export let UserPermissions = class UserPermissions {
static canCreateContent () {
if (Roles.userIsInRole(Meteor.userId(), ['admin', 'editor', 'university', 'lecturer', 'pro']) && this.isNotBlocked()) {
if (Roles.userIsInRole(Meteor.userId(), ['admin', 'editor', 'university', 'lecturer', 'pro']) && this.isNotBlockedOrFirstLogin()) {
return true;
}
}
static isAdmin () {
if (Roles.userIsInRole(Meteor.userId(), ['admin', 'editor']) && this.isNotBlocked()) {
if (Roles.userIsInRole(Meteor.userId(), ['admin', 'editor']) && this.isNotBlockedOrFirstLogin()) {
return true;
}
}
static isNotBlockedOrFirstLogin () {
return !Roles.userIsInRole(Meteor.userId(), ['blocked', 'firstLogin']);
}
static isNotBlocked () {
return !Roles.userIsInRole(Meteor.userId(), ['firstLogin', 'blocked']);
return !Roles.userIsInRole(Meteor.userId(), ['blocked']);
}
static isOwner (content_owner) {
......
......@@ -2,83 +2,59 @@ import {Meteor} from "meteor/meteor";
import {Mongo} from "meteor/mongo";
import {Cardsets} from "./cardsets.js";
import {check} from "meteor/check";
import {UserPermissions} from "./permissions";
export const Ratings = new Mongo.Collection("ratings");
if (Meteor.isServer) {
Meteor.publish("ratings", function () {
if (this.userId && !Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
return Ratings.find();
} else {
return [];
Meteor.publish("cardsetUserRating", function (cardset_id) {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin()) {
return Ratings.find({cardset_id: cardset_id, user_id: this.userId});
}
});
}
Meteor.methods({
addRating: function (cardset_id, rating) {
rateCardset: function (cardset_id, rating) {
check(cardset_id, String);
check(rating, Number);
// Make sure the user is logged in
if (!Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
if (Cardsets.findOne({_id: cardset_id}).owner === Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Ratings.insert({
cardset_id: cardset_id,
user: Meteor.userId(),
rating: rating
});
Meteor.call("updateRelevance", cardset_id, function (error, relevance) {
if (!error) {
Cardsets.update(cardset_id, {
if (this.userId && UserPermissions.isNotBlockedOrFirstLogin() && !UserPermissions.isOwner(Cardsets.findOne({_id: cardset_id}).owner)) {
if (Ratings.findOne({cardset_id: cardset_id, user_id: this.userId})) {
Ratings.update({cardset_id: cardset_id, user_id: this.userId}, {
$set: {
relevance: Number(relevance),
raterCount: Number(Ratings.find({cardset_id: cardset_id}).count())
rating: rating
}
});
} else {
Ratings.insert({
cardset_id: cardset_id,
user_id: this.userId,
rating: rating
});
}
});
},
updateRating: function (cardset_id, rating) {
check(cardset_id, String);
check(rating, Number);
// Make sure the user is logged in
if (!Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
if (Cardsets.findOne({_id: cardset_id}).owner === Meteor.userId()) {
Meteor.call('updateCardsetRating', cardset_id);
} else {
throw new Meteor.Error("not-authorized");
}
Ratings.update({
cardset_id: cardset_id,
user: Meteor.userId()
},
{
$set: {
rating: rating
}
},
updateCardsetRating: function (cardset_id) {
check(cardset_id, String);
if (Meteor.isServer) {
let ratings = Ratings.find({cardset_id: cardset_id}).fetch();
let sum = 0;
for (let i = 0; i < ratings.length; i++) {
sum += ratings[i].rating;
}
);
Meteor.call("updateRelevance", cardset_id, function (error, relevance) {
if (!error) {
Cardsets.update(cardset_id, {
$set: {
relevance: Number(relevance),
raterCount: Number(Ratings.find({cardset_id: cardset_id}).count())
}
});
if (sum !== 0) {
sum = (sum / ratings.length).toFixed(2);
}
});
Cardsets.update(cardset_id, {
$set: {
rating: sum,
raterCount: ratings.length
}
});
}
}
});
......@@ -7,6 +7,7 @@ import {check} from "meteor/check";
import {Session} from "meteor/session";
import {UserPermissions} from "./permissions";
import {WebPushSubscriptions} from "./webPushSubscriptions";
import {Paid} from "./paid";
/**
* Returns the degree, the givenname and the birthname from the author of a cardset
......@@ -90,6 +91,7 @@ if (Meteor.isServer) {
'profile.givenname': 1,
'profile.title': 1,
'profile.locale': 1,
'count.cardsets': 1,
'email': 1,
'services': 1,
'lvl': 1,
......@@ -98,7 +100,7 @@ if (Meteor.isServer) {
'daysInRow': 1,
'customerId': 1,
'blockedtext': 1,
"selectedColorTheme": "default",
"selectedColorTheme": 1,
"selectedLanguage": 1,
"mailNotification": 1,
"webNotification": 1
......@@ -121,6 +123,7 @@ if (Meteor.isServer) {
'profile.givenname': 1,
'profile.title': 1,
'profile.locale': 1,
'count.cardsets': 1,
'email': 1,
'services': 1,
'lvl': 1,
......@@ -130,7 +133,8 @@ if (Meteor.isServer) {
'balance': 1,
"mailNotification": 1,
"webNotification": 1,
"selectedLanguage": 1
"selectedLanguage": 1,
"selectedColorTheme": 1
}
});
} else {
......@@ -431,11 +435,15 @@ Meteor.methods({
}
Ratings.remove({