Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
tibetsprague committed Jun 9, 2022
2 parents 7a69c5c + 26da5f4 commit 50037ec
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 24 deletions.
32 changes: 29 additions & 3 deletions APIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ __Parameters:__
- name (required) = Judy Mangrove
- email (required) = email@email.com
- groupId (optional) = the id of a group to add the user to
- isModerator (optional) = true to add the user to the group specified by groupId as a moderator

__Return value__:

Expand All @@ -47,9 +48,14 @@ On success this will return a JSON object that looks like:
}
```

If there is already a user with this email registered you will receive:
`{ "message": "User already exists" }`
If there is already a user with this email but they are a not member of the group, this call will send them an invitation to join the group. You will receive:
{ message: `User already exists, invite sent to group GROUP_NAME` }

If there is already a user with this email and they are already a member of the group:
{ message: `User already exists, and is already a member of this group` }

If there is already a user with this email and you didn't pass in a group you will receive:
`{ "message": "User already exists" }`

### Create a Group

Expand Down Expand Up @@ -97,7 +103,7 @@ Example GraphQL mutation:
locationDisplayPrecision: precise, // precise => precise location displayed, near => location text shows nearby town/city and coordinate shifted, region => location not shown on map at all and location text shows nearby city/region
publicMemberDirectory: false, // Boolean
},
"type": "farm", // Optionally set the group type to farm, don't pass in for regular groups
"type": <valid type or empty for default group type>,
"typeDescriptor": "Ranch", // Group is the default
"typeDescriptorPlural": "Ranches" // Groups is the default
},
Expand Down Expand Up @@ -128,6 +134,26 @@ Example GraphQL mutation:
}
```

### Add a Person to a Group

`POST to https://hylo.com/noo/graphql`

__Headers:__
Content-Type: application/json

This is a GraphQL based endpoint so you will want the pass in a raw POST data
Example GraphQL mutation:
```
{
"query": "mutation ($userId: ID, $groupId: ID, $role: Int) { addMember(userId: $userId, groupId: $groupId, role: $role) { success error } }",
"variables": {
"groupId": USER_ID,
"groupId": GROUP_ID,
"role": 0 // 0 = regular member, 1 = Moderator
}
}
```

### Query a Group

`POST to https://hylo.com/noo/graphql`
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [4.1.2] - 2022-06-08

### Added
- Add user id to member export CSV
- Add role parameter to createUser api call
- New API call addMember to add person to a group
- Group type can be set when creating a group

### Fixed
- Return groups that match a bounding box if they have a geoShape but no location
- Geocoding of locations when only a location string is passed in to create a group

## [4.1.1] - 2022-05-26

### Added
Expand Down
4 changes: 2 additions & 2 deletions api/controllers/ExportController.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async function exportMembers(groupId, req, email) {
await Promise.all(users.map((u, idx) => {
// pluck core user data into results
results.push(u.pick([
'name', 'contact_email', 'contact_phone', 'avatar_url', 'tagline', 'bio',
'id', 'name', 'contact_email', 'contact_phone', 'avatar_url', 'tagline', 'bio',
'url', 'twitter_name', 'facebook_url', 'linkedin_url'
]))

Expand Down Expand Up @@ -122,7 +122,7 @@ async function exportMembers(groupId, req, email) {

// send data as CSV response
output(results, [
'name', 'contact_email', 'contact_phone', 'location', 'avatar_url', 'tagline', 'bio',
'id', 'name', 'contact_email', 'contact_phone', 'location', 'avatar_url', 'tagline', 'bio',
{ key: 'url', header: 'personal_url' },
'twitter_name', 'facebook_url', 'linkedin_url',
'skills', 'skills_to_learn',
Expand Down
19 changes: 14 additions & 5 deletions api/controllers/UserController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import OIDCAdapter from '../services/oidc/KnexAdapter'
module.exports = {

create: async function (req, res) {
const { name, email, groupId } = req.allParams()
const { name, email, groupId, isModerator } = req.allParams()
const group = groupId && await Group.find(groupId)
const isModeratorVal = isModerator && isModerator === 'true'

let user = await User.find(email, {}, false)
if (user) {
Expand All @@ -26,18 +27,26 @@ module.exports = {
const inviteBy = await group.moderators().fetchOne()

await InvitationService.create({
sessionUserId: inviteBy?.id,
groupId: group.id,
userIds: [user.id],
isModerator: isModeratorVal,
message,
subject
sessionUserId: inviteBy?.id,
subject,
userIds: [user.id]
})
return res.ok({ message: `User already exists, invite sent to group ${group.get('name')}` })
}
return res.ok({ message: `User already exists, and is already a member of this group` })
}
return res.ok({ message: "User already exists" })
}

return User.create({name, email: email ? email.toLowerCase() : null, email_validated: false, active: false, group })
const attrs = { name, email: email ? email.toLowerCase() : null, email_validated: false, active: false, group }
if (isModeratorVal) {
attrs['role'] = GroupMembership.Role.MODERATOR
}

return User.create(attrs)
.then(async (user) => {
Queue.classMethod('Email', 'sendFinishRegistration', {
email,
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { presentQuerySet } from '../../lib/graphql-bookshelf-bridge/util'
import {
acceptGroupRelationshipInvite,
acceptJoinRequest,
addMember,
addModerator,
addPeopleToProjectRole,
addSkill,
Expand Down Expand Up @@ -435,6 +436,7 @@ export function makeApiQueries (fetchOne) {

export function makeApiMutations () {
return {
addMember: (root, { userId, groupId, role }) => addMember(userId, groupId, role),
createGroup: (root, { asUserId, data }) => createGroup(asUserId, data),
updateGroup: (root, { asUserId, id, changes }) => updateGroup(asUserId, id, changes)
}
Expand Down
13 changes: 13 additions & 0 deletions api/graphql/mutations/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,16 @@ export async function rejectGroupRelationshipInvite (userId, groupRelationshipIn
throw new Error(`Invalid parameters to reject invite`)
}
}

// API only Group Mutations
export async function addMember (userId, groupId, role) {
const group = await Group.find(groupId)
if (!group) {
return { success: false, error: 'Group not found' }
}

if (group) {
await group.addMembers([userId], { role: role || GroupMembership.Role.DEFAULT }, {})
}
return { success: true }
}
1 change: 1 addition & 0 deletions api/graphql/mutations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
} from './event'
export {
acceptGroupRelationshipInvite,
addMember,
addModerator,
cancelGroupRelationshipInvite,
createGroup,
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ input GroupInput {
slackTeam: String
slackConfigureUrl: String
slug: String
type: String
typeDescriptor: String
typeDescriptorPlural: String
visibility: Int
Expand Down Expand Up @@ -965,6 +966,7 @@ input GroupWidgetSettingsInput {
type Mutation {
acceptGroupRelationshipInvite(groupRelationshipInviteId: ID): AcceptGroupRelationshipInviteResult
acceptJoinRequest(joinRequestId: ID): JoinRequest
addMember(userId: ID, groupId: ID, role: Int): GenericResult
addModerator(personId: ID, groupId: ID): Group
addPeopleToProjectRole(peopleIds: [ID], projectRoleId: ID): GenericResult
addSkill(name: String, type: Int): Skill
Expand Down
6 changes: 3 additions & 3 deletions api/models/Group.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ module.exports = bookshelf.Model.extend(merge({
pick(data,
'about_video_uri', 'accessibility', 'avatar_url', 'description', 'slug', 'category',
'access_code', 'banner_url', 'location_id', 'location', 'group_data_type', 'moderator_descriptor',
'moderator_descriptor_plural', 'name', 'type_descriptor', 'type_descriptor_plural', 'visibility'
'moderator_descriptor_plural', 'name', 'type', 'type_descriptor', 'type_descriptor_plural', 'visibility'
),
{
'accessibility': Group.Accessibility.RESTRICTED,
Expand Down Expand Up @@ -578,11 +578,11 @@ module.exports = bookshelf.Model.extend(merge({
geocoder.forwardGeocode({
mode: 'mapbox.places-permanent',
query: group.get('location')
}).send().then(response => {
}).send().then(async (response) => {
const match = response.body
if (match?.features && match?.features.length > 0) {
const locationData = omit(LocationHelpers.convertMapboxToLocation(match.features[0]), 'mapboxId')
const loc = findOrCreateLocation(locationData)
const loc = await findOrCreateLocation(locationData)
group.save({ location_id: loc.id })
}
})
Expand Down
6 changes: 3 additions & 3 deletions api/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ module.exports = bookshelf.Model.extend(merge({
},

create: function (attributes) {
const { account, group } = attributes
const { account, group, role } = attributes

attributes = merge({
avatar_url: User.gravatar(attributes.email),
Expand All @@ -613,7 +613,7 @@ module.exports = bookshelf.Model.extend(merge({
comment_notifications: 'both'
},
active: true
}, omit(attributes, 'account', 'group'))
}, omit(attributes, 'account', 'group', 'role'))

if (account) {
merge(
Expand All @@ -628,7 +628,7 @@ module.exports = bookshelf.Model.extend(merge({
.then(async (user) => {
await Promise.join(
account && LinkedAccount.create(user.id, account, {transacting}),
group && group.addMembers([user.id], {}, {transacting}),
group && group.addMembers([user.id], { role: role || GroupMembership.Role.DEFAULT }, {transacting}),
group && user.markInvitationsUsed(group.id, transacting)
)
return user
Expand Down
11 changes: 8 additions & 3 deletions api/services/Search/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export const filterAndSortUsers = curry(({ autocomplete, boundingBox, order, sea

if (boundingBox) {
q.join('locations', 'locations.id', '=', 'users.location_id')
q.whereRaw('locations.center && ST_MakeEnvelope(?, ?, ?, ?, 4326)', [boundingBox[0].lng, boundingBox[0].lat, boundingBox[1].lng, boundingBox[1].lat])
const bb = [boundingBox[0].lng, boundingBox[0].lat, boundingBox[1].lng, boundingBox[1].lat]
q.whereRaw('locations.center && ST_MakeEnvelope(?, ?, ?, ?, 4326)', bb)
}
})

Expand All @@ -157,8 +158,12 @@ export const filterAndSortGroups = curry((opts, q) => {
}

if (boundingBox) {
q.join('locations', 'locations.id', '=', 'groups.location_id')
q.whereRaw('locations.center && ST_MakeEnvelope(?, ?, ?, ?, 4326)', [boundingBox[0].lng, boundingBox[0].lat, boundingBox[1].lng, boundingBox[1].lat])
q.leftJoin('locations', 'locations.id', '=', 'groups.location_id')
const bb = [boundingBox[0].lng, boundingBox[0].lat, boundingBox[1].lng, boundingBox[1].lat]
q.where(q2 =>
q2.whereRaw('locations.center && ST_MakeEnvelope(?, ?, ?, ?, 4326)', bb)
.orWhereRaw('ST_Intersects(groups.geo_shape, ST_MakeEnvelope(?, ?, ?, ?, 4326))', bb)
)
}

if (sortBy === 'size') {
Expand Down
12 changes: 9 additions & 3 deletions migrations/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,12 @@ CREATE TABLE public.groups (
slack_team text,
slack_configure_url text,
type text,
geo_shape public.geometry(Polygon,4326)
geo_shape public.geometry(Polygon,4326),
type_descriptor character varying(255) DEFAULT NULL::character varying,
type_descriptor_plural character varying(255) DEFAULT NULL::character varying,
moderator_descriptor character varying(255) DEFAULT NULL::character varying,
moderator_descriptor_plural character varying(255) DEFAULT NULL::character varying,
about_video_uri character varying(255)
);


Expand Down Expand Up @@ -1158,11 +1163,12 @@ CREATE TABLE public.locations (
region character varying(255),
neighborhood character varying(255),
postcode character varying(255),
country character varying(255),
country_code character varying(255),
accuracy character varying(255),
wikidata character varying(255),
created_at timestamp with time zone,
updated_at timestamp with time zone
updated_at timestamp with time zone,
country character varying(255)
);


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Tibet Sprague <tibet@hylo.com>",
"license": "GNU AFFERO GENERAL PUBLIC LICENSE v3",
"private": true,
"version": "4.1.1",
"version": "4.1.2",
"repository": {
"type": "git",
"url": "git://github.com/Hylozoic/hylo-node.git"
Expand Down
2 changes: 1 addition & 1 deletion test/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var TestSetup = function () {
var setup = new TestSetup()

before(function (done) {
this.timeout(30000)
this.timeout(50000)

var i18n = require('i18n')
i18n.configure(require(root('config/i18n')).i18n)
Expand Down

0 comments on commit 50037ec

Please sign in to comment.