diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index de171cc61..cc4d2deb9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -202,6 +202,8 @@ jobs:
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps neo4j backend
- name: backend | Initialize Database
run: docker-compose exec -T backend yarn db:migrate init
+ - name: backend | Migrate Database Up
+ run: docker-compose exec -T backend yarn db:migrate up
- name: backend | Unit test
run: docker-compose exec -T backend yarn test
##########################################################################
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index d77363c29..3d698810e 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -302,6 +302,7 @@ export default shield(
searchResults: allow,
searchPosts: allow,
searchUsers: allow,
+ searchGroups: allow,
searchHashtags: allow,
embed: allow,
Category: allow,
diff --git a/backend/src/schema/resolvers/searches.js b/backend/src/schema/resolvers/searches.js
index 63279b4bf..3fdf22da3 100644
--- a/backend/src/schema/resolvers/searches.js
+++ b/backend/src/schema/resolvers/searches.js
@@ -66,6 +66,21 @@ const searchHashtagsSetup = {
limit: 'LIMIT $limit',
}
+const searchGroupsSetup = {
+ fulltextIndex: 'group_fulltext_search',
+ match: `MATCH (resource:Group)
+ MATCH (user:User {id: $userId})
+ OPTIONAL MATCH (user)-[membership:MEMBER_OF]->(resource)
+ WITH user, resource, membership`,
+ whereClause: `WHERE score >= 0.0
+ AND NOT (resource.deleted = true OR resource.disabled = true)
+ AND (resource.groupType IN ['public', 'closed']
+ OR membership.role IN ['usual', 'admin', 'owner'])`,
+ withClause: 'WITH resource, membership',
+ returnClause: 'resource { .*, myRole: membership.role, __typename: labels(resource)[0] }',
+ limit: 'LIMIT $limit',
+}
+
const countSetup = {
returnClause: 'toString(size(collect(resource)))',
limit: '',
@@ -83,6 +98,10 @@ const countHashtagsSetup = {
...searchHashtagsSetup,
...countSetup,
}
+const countGroupsSetup = {
+ ...searchGroupsSetup,
+ ...countSetup,
+}
const searchResultPromise = async (session, setup, params) => {
return session.readTransaction(async (transaction) => {
@@ -113,6 +132,7 @@ const multiSearchMap = [
{ symbol: '!', setup: searchPostsSetup, resultName: 'posts' },
{ symbol: '@', setup: searchUsersSetup, resultName: 'users' },
{ symbol: '#', setup: searchHashtagsSetup, resultName: 'hashtags' },
+ { symbol: '&', setup: searchGroupsSetup, resultName: 'groups' },
]
export default {
@@ -178,13 +198,36 @@ export default {
}),
}
},
+ searchGroups: async (_parent, args, context, _resolveInfo) => {
+ const { query, groupsOffset, firstGroups } = args
+ let userId = null
+ if (context.user) userId = context.user.id
+ return {
+ groupCount: getSearchResults(
+ context,
+ countGroupsSetup,
+ {
+ query: queryString(query),
+ skip: 0,
+ userId,
+ },
+ countResultCallback,
+ ),
+ groups: getSearchResults(context, searchGroupsSetup, {
+ query: queryString(query),
+ skip: groupsOffset,
+ limit: firstGroups,
+ userId,
+ }),
+ }
+ },
searchResults: async (_parent, args, context, _resolveInfo) => {
const { query, limit } = args
let userId = null
if (context.user) userId = context.user.id
- const searchType = query.replace(/^([!@#]?).*$/, '$1')
- const searchString = query.replace(/^([!@#])/, '')
+ const searchType = query.replace(/^([!@#&]?).*$/, '$1')
+ const searchString = query.replace(/^([!@#&])/, '')
const params = {
query: queryString(searchString),
@@ -197,6 +240,7 @@ export default {
return [
...(await getSearchResults(context, searchPostsSetup, params)),
...(await getSearchResults(context, searchUsersSetup, params)),
+ ...(await getSearchResults(context, searchGroupsSetup, params)),
...(await getSearchResults(context, searchHashtagsSetup, params)),
]
diff --git a/backend/src/schema/types/type/Search.gql b/backend/src/schema/types/type/Search.gql
index 9537b5a84..5cb68e22d 100644
--- a/backend/src/schema/types/type/Search.gql
+++ b/backend/src/schema/types/type/Search.gql
@@ -1,4 +1,4 @@
-union SearchResult = Post | User | Tag
+union SearchResult = Post | User | Tag | Group
type postSearchResults {
postCount: Int
@@ -15,9 +15,15 @@ type hashtagSearchResults {
hashtags: [Tag]!
}
+type groupSearchResults {
+ groupCount: Int
+ groups: [Group]!
+}
+
type Query {
searchPosts(query: String!, firstPosts: Int, postsOffset: Int): postSearchResults!
searchUsers(query: String!, firstUsers: Int, usersOffset: Int): userSearchResults!
+ searchGroups(query: String!, firstGroups: Int, groupsOffset: Int): groupSearchResults!
searchHashtags(query: String!, firstHashtags: Int, hashtagsOffset: Int): hashtagSearchResults!
searchResults(query: String!, limit: Int = 5): [SearchResult]!
}
diff --git a/webapp/components/_new/features/SearchResults/SearchResults.spec.js b/webapp/components/_new/features/SearchResults/SearchResults.spec.js
index b76226547..19653fcac 100644
--- a/webapp/components/_new/features/SearchResults/SearchResults.spec.js
+++ b/webapp/components/_new/features/SearchResults/SearchResults.spec.js
@@ -36,6 +36,7 @@ describe('SearchResults', () => {
}
propsData = {
pageSize: 12,
+ search: '',
}
wrapper = Wrapper()
})
@@ -169,7 +170,7 @@ describe('SearchResults', () => {
await wrapper.vm.$nextTick()
await expect(
wrapper.vm.$options.apollo.searchPosts.variables.bind(wrapper.vm)(),
- ).toMatchObject({ query: undefined, firstPosts: 12, postsOffset: 12 })
+ ).toMatchObject({ query: '', firstPosts: 12, postsOffset: 12 })
})
it('displays the next page button when next-button is clicked', async () => {
@@ -199,7 +200,7 @@ describe('SearchResults', () => {
await wrapper.vm.$nextTick()
await expect(
wrapper.vm.$options.apollo.searchPosts.variables.bind(wrapper.vm)(),
- ).toMatchObject({ query: undefined, firstPosts: 12, postsOffset: 24 })
+ ).toMatchObject({ query: '', firstPosts: 12, postsOffset: 24 })
})
it('deactivates next page button when next-button is clicked twice', async () => {
@@ -234,7 +235,7 @@ describe('SearchResults', () => {
await wrapper.vm.$nextTick()
await expect(
wrapper.vm.$options.apollo.searchPosts.variables.bind(wrapper.vm)(),
- ).toMatchObject({ query: undefined, firstPosts: 12, postsOffset: 0 })
+ ).toMatchObject({ query: '', firstPosts: 12, postsOffset: 0 })
})
})
})
diff --git a/webapp/components/_new/features/SearchResults/SearchResults.vue b/webapp/components/_new/features/SearchResults/SearchResults.vue
index f3730f61d..20385ce64 100644
--- a/webapp/components/_new/features/SearchResults/SearchResults.vue
+++ b/webapp/components/_new/features/SearchResults/SearchResults.vue
@@ -59,6 +59,14 @@
+
+
+ {{ option.groupName | truncate(70) }}
+
{ if (!this.isTag(item)) { this.$router.push({ - name: this.isPost(item) ? 'post-id-slug' : 'profile-id-slug', + name: this.getRouteName(item), params: { id: item.id, slug: item.slug }, }) } else { diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js index 68cd02c6a..23d2c11d3 100644 --- a/webapp/graphql/Fragments.js +++ b/webapp/graphql/Fragments.js @@ -62,6 +62,29 @@ export const postFragment = gql` } ` +export const groupFragment = gql` + fragment group on Group { + id + groupName: name + slug + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + locationName + myRole + } +` + export const postCountsFragment = gql` fragment postCounts on Post { commentsCount diff --git a/webapp/graphql/Search.js b/webapp/graphql/Search.js index 56f5d7c4c..b8c4fcb51 100644 --- a/webapp/graphql/Search.js +++ b/webapp/graphql/Search.js @@ -1,9 +1,15 @@ import gql from 'graphql-tag' -import { userFragment, postFragment, tagsCategoriesAndPinnedFragment } from './Fragments' +import { + userFragment, + postFragment, + groupFragment, + tagsCategoriesAndPinnedFragment, +} from './Fragments' export const searchQuery = gql` ${userFragment} ${postFragment} + ${groupFragment} query ($query: String!) { searchResults(query: $query, limit: 5) { @@ -24,6 +30,9 @@ export const searchQuery = gql` ... on Tag { id } + ... on Group { + ...group + } } } ` @@ -52,6 +61,46 @@ export const searchPosts = gql` } ` +export const searchGroups = (i18n) => { + const lang = i18n ? i18n.locale().toUpperCase() : 'EN' + return gql` + query ($query: String!, $firstGroups: Int, $groupsOffset: Int) { + searchGroups(query: $query, firstGroups: $firstGroups, groupsOffset: $groupsOffset) { + groupCount + groups { + __typename + id + groupName: name + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + avatar { + url + } + locationName + location { + name: name${lang} + } + myRole + } + } + } + ` +} + export const searchUsers = gql` ${userFragment} diff --git a/webapp/locales/de.json b/webapp/locales/de.json index be47f7c54..67c88e4f4 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -745,11 +745,12 @@ "failed": "Nichts gefunden", "for": "Suche nach ", "heading": { + "Group": "Gruppe ::: Gruppen", "Post": "Beitrag ::: Beiträge", "Tag": "Hashtag ::: Hashtags", "User": "Benutzer ::: Benutzer" }, - "hint": "Wonach suchst Du? Nutze !… für Beiträge, @… für Mitglieder, #… für Hashtags", + "hint": "Wonach suchst Du? Nutze !… für Beiträge, @… für Mitglieder, &… für Gruppen, #… für Hashtags", "no-results": "Keine Ergebnisse für \"{search}\" gefunden. Versuch' es mit einem anderen Begriff!", "page": "Seite", "placeholder": "Suchen", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index ae4c9db60..ba1d65881 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -745,11 +745,12 @@ "failed": "Nothing found", "for": "Searching for ", "heading": { + "Group": "Group ::: Groups", "Post": "Post ::: Posts", "Tag": "Hashtag ::: Hashtags", "User": "User ::: Users" }, - "hint": "What are you searching for? Use !… for posts, @… for users, #… for hashtags.", + "hint": "What are you searching for? Use !… for posts, @… for users, &… for groups, #… for hashtags.", "no-results": "No results found for \"{search}\". Try a different search term!", "page": "Page", "placeholder": "Search",