mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-13 15:38:59 +00:00
Added paging (#10150)
* Added paging * Escaped regex * Fixed challenge side effect tests
This commit is contained in:
parent
0f339d8d3e
commit
298a6a743c
5 changed files with 145 additions and 18 deletions
|
|
@ -227,4 +227,53 @@ describe('GET challenges/user', () => {
|
|||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('filters and paging', () => {
|
||||
let user, guild, member;
|
||||
const categories = [{
|
||||
slug: 'newCat',
|
||||
name: 'New Category',
|
||||
}];
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestGuild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
user = groupLeader;
|
||||
guild = group;
|
||||
member = members[0];
|
||||
|
||||
for (let i = 0; i < 11; i += 1) {
|
||||
await generateChallenge(user, group); // eslint-disable-line
|
||||
}
|
||||
});
|
||||
|
||||
it('returns public guilds filtered by category', async () => {
|
||||
const categoryChallenge = await generateChallenge(user, guild, {categories});
|
||||
const challenges = await user.get(`/challenges/user?categories=${categories[0].slug}`);
|
||||
|
||||
expect(challenges[0]._id).to.eql(categoryChallenge._id);
|
||||
expect(challenges.length).to.eql(1);
|
||||
});
|
||||
|
||||
it('paginates challenges', async () => {
|
||||
const challenges = await user.get('/challenges/user');
|
||||
const challengesPaged = await user.get('/challenges/user?page=1&owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(10);
|
||||
expect(challengesPaged.length).to.eql(2);
|
||||
});
|
||||
|
||||
it('filters by owned', async () => {
|
||||
const challenges = await member.get('/challenges/user?owned=owned');
|
||||
|
||||
expect(challenges.length).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
.row
|
||||
.col-12.col-md-6(v-for='challenge in filteredChallenges', v-if='!memberOf(challenge)')
|
||||
challenge-item(:challenge='challenge')
|
||||
.row
|
||||
.col-12.text-center
|
||||
button.btn.btn-secondary(@click='loadMore()') {{ $t('loadMore') }}
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
|
@ -89,23 +92,16 @@ export default {
|
|||
],
|
||||
search: '',
|
||||
filters: {},
|
||||
page: 0,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.loadchallanges();
|
||||
|
||||
// @TODO: do we need to load groups for filters still?
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
filteredChallenges () {
|
||||
let search = this.search;
|
||||
let filters = this.filters;
|
||||
let user = this.$store.state.user.data;
|
||||
// @TODO: Move this to the server
|
||||
return this.challenges.filter((challenge) => {
|
||||
return this.filterChallenge(challenge, filters, search, user);
|
||||
});
|
||||
return this.challenges;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -114,21 +110,53 @@ export default {
|
|||
},
|
||||
updateSearch (eventData) {
|
||||
this.search = eventData.searchTerm;
|
||||
this.page = 0;
|
||||
this.loadchallanges();
|
||||
},
|
||||
updateFilters (eventData) {
|
||||
this.filters = eventData;
|
||||
this.page = 0;
|
||||
this.loadchallanges();
|
||||
},
|
||||
createChallenge () {
|
||||
this.$root.$emit('bv::show::modal', 'challenge-modal');
|
||||
},
|
||||
async loadchallanges () {
|
||||
this.loading = true;
|
||||
this.challenges = await this.$store.dispatch('challenges:getUserChallenges');
|
||||
|
||||
let categories = '';
|
||||
if (this.filters.categories) {
|
||||
categories = this.filters.categories.join(',');
|
||||
}
|
||||
|
||||
let owned = '';
|
||||
// @TODO: we skip ownership === 2 because it is the same as === 0 right now
|
||||
if (this.filters.ownership && this.filters.ownership.length === 1) {
|
||||
owned = this.filters.ownership[0];
|
||||
}
|
||||
|
||||
const challenges = await this.$store.dispatch('challenges:getUserChallenges', {
|
||||
page: this.page,
|
||||
search: this.search,
|
||||
categories,
|
||||
owned,
|
||||
});
|
||||
|
||||
if (this.page === 0) {
|
||||
this.challenges = challenges;
|
||||
} else {
|
||||
this.challenges = this.challenges.concat(challenges);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
challengeCreated (challenge) {
|
||||
this.challenges.push(challenge);
|
||||
},
|
||||
async loadMore () {
|
||||
this.page += 1;
|
||||
this.loadchallanges();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export default {
|
|||
this.$emit('search', {
|
||||
searchTerm: newSearch,
|
||||
});
|
||||
}, 250),
|
||||
}, 500),
|
||||
},
|
||||
methods: {
|
||||
emitFilters () {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import axios from 'axios';
|
||||
import omit from 'lodash/omit';
|
||||
import encodeParams from 'client/libs/encodeParams';
|
||||
|
||||
export async function createChallenge (store, payload) {
|
||||
let response = await axios.post('/api/v3/challenges', payload.challenge);
|
||||
|
|
@ -34,8 +35,24 @@ export async function leaveChallenge (store, payload) {
|
|||
|
||||
export async function getUserChallenges (store, payload) {
|
||||
let url = '/api/v3/challenges/user';
|
||||
if (payload && payload.member) url += '?member=true';
|
||||
let response = await axios.get(url);
|
||||
let {
|
||||
member,
|
||||
page,
|
||||
search,
|
||||
categories,
|
||||
owned,
|
||||
} = payload;
|
||||
|
||||
let query = {};
|
||||
if (member) query.member = member;
|
||||
if (page) query.page = page;
|
||||
if (search) query.search = search;
|
||||
if (categories) query.categories = categories;
|
||||
if (owned) query.owned = owned;
|
||||
|
||||
const parms = encodeParams(query);
|
||||
const response = await axios.get(`${url}?${parms}`);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -340,22 +340,55 @@ api.getUserChallenges = {
|
|||
url: '/challenges/user',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
const CHALLENGES_PER_PAGE = 10;
|
||||
const page = req.query.page || 0;
|
||||
|
||||
const user = res.locals.user;
|
||||
let orOptions = [
|
||||
{_id: {$in: user.challenges}}, // Challenges where the user is participating
|
||||
{leader: user._id}, // Challenges where I'm the leader
|
||||
];
|
||||
|
||||
const owned = req.query.owned;
|
||||
if (!owned) {
|
||||
orOptions.push({leader: user._id});
|
||||
}
|
||||
|
||||
if (!req.query.member) {
|
||||
orOptions.push({
|
||||
group: {$in: user.getGroups()},
|
||||
}); // Challenges in groups where I'm a member
|
||||
}
|
||||
|
||||
let challenges = await Challenge.find({
|
||||
$or: orOptions,
|
||||
})
|
||||
let query = {
|
||||
$and: [{$or: orOptions}],
|
||||
};
|
||||
|
||||
if (owned && owned === 'not_owned') {
|
||||
query.$and.push({leader: {$ne: user._id}});
|
||||
}
|
||||
|
||||
if (owned && owned === 'owned') {
|
||||
query.$and.push({leader: user._id});
|
||||
}
|
||||
|
||||
if (req.query.search) {
|
||||
const searchOr = {$or: []};
|
||||
const searchWords = _.escapeRegExp(req.query.search).split(' ').join('|');
|
||||
const searchQuery = { $regex: new RegExp(`${searchWords}`, 'i') };
|
||||
searchOr.$or.push({name: searchQuery});
|
||||
searchOr.$or.push({description: searchQuery});
|
||||
query.$and.push(searchOr);
|
||||
}
|
||||
|
||||
if (req.query.categories) {
|
||||
let categorySlugs = req.query.categories.split(',');
|
||||
query.categories = { $elemMatch: { slug: {$in: categorySlugs} } };
|
||||
}
|
||||
|
||||
const challenges = await Challenge.find(query)
|
||||
.sort('-createdAt')
|
||||
.limit(CHALLENGES_PER_PAGE)
|
||||
.skip(CHALLENGES_PER_PAGE * page)
|
||||
// see below why we're not using populate
|
||||
// .populate('group', basicGroupFields)
|
||||
// .populate('leader', nameFields)
|
||||
|
|
|
|||
Loading…
Reference in a new issue