Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 63 additions & 4 deletions Net.Pokeshot.JiveSdk/Clients/DESClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
using System;
using System.Web;
using Newtonsoft.Json.Linq;
using System.Threading;

namespace Net.Pokeshot.JiveSdk.Clients
{
public class DESClient : JiveClient
{
static volatile string _token = null;
static Mutex mut = new Mutex();

string _desUrl { get { return "https://api.jivesoftware.com/analytics/v2/export"; } }
string _clientId;
string _clientSecret;
Expand All @@ -23,8 +27,9 @@ public List<JiveDEAActivityInstance> GetActivity(List<string> filter = null, int
{
// Jive's DES server seems to off by a few seconds. When making calls using before or after, if either is ahead of the Jive's server, we get a 400 Bad Request Error.
// For that reason, we push back the these values by 20 seconds. It should be noted that this is problem may get resolved later or not appear on certain clients.
before = before?.AddSeconds(-20);
after = after?.AddSeconds(-20);
// Still getting these errors every once in a while, so bumping up to 30 seconds
before = before?.AddSeconds(-30);
after = after?.AddSeconds(-30);

List<JiveDEAActivityInstance> activityList = new List<JiveDEAActivityInstance>();

Expand Down Expand Up @@ -69,6 +74,9 @@ public List<JiveDEAActivityInstance> GetActivity(List<string> filter = null, int
throw new HttpException(e.WebEventCode, "An input field is missing or malformed", e);
case 403:
throw new HttpException(e.WebEventCode, "You are not allowed to access this", e);
case 401: // If the token happens to have expired, try once more before giving up.
json = retry(url);
break;
default:
throw;
}
Expand All @@ -86,17 +94,68 @@ public List<JiveDEAActivityInstance> GetActivity(List<string> filter = null, int
return activityList;
}

private string retry(string url, int numTries = 0)
{
int maxTry = 10;
try
{
return GetAbsolute(url, getAuthorization());
}
catch (HttpException e)
{
if (e.GetHttpCode() == 401)
{
if (numTries > maxTry)
throw;

return retry(url, ++numTries);
}
else
throw;
}
}

/// <summary>
/// Gets the Authorization needed for downloading data from the DES.
/// Gets the Authorization needed for downloading data from the DES. This method is thread safe.
/// </summary>
/// <returns>string to be used as the authorization header for web request to the des.</returns>
private string getAuthorization() {
string url = "https://api.jivesoftware.com/analytics/v1/auth/login?";
url += "clientId=" + _clientId;
url += "&clientSecret=" + _clientSecret;

return PostAbsolute(url, "");
mut.WaitOne();
// The token expires after 30 minutes. However, if a new one is created, the old one quits working.
if (!isValid(_token))
{
_token = PostAbsolute(url, "");
}
mut.ReleaseMutex();

return _token;
}

/// <summary>
/// Tries the token. If it works, returns true. Otherwise, returns false.
/// </summary>
/// <param name="_token"></param>
/// <returns></returns>
private bool isValid(string _token)
{
if (_token == null)
return false;

var url = _desUrl + "/activity?count=1&fields=actorID";
try
{
GetAbsolute(url, _token);
}
catch (Exception)
{
return false;
}

return true;
}
}
}
24 changes: 17 additions & 7 deletions Net.Pokeshot.JiveSdk/Clients/IdeaVotesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@ public class IdeaVotesClient : JiveClient
public IdeaVotesClient(string communityUrl, NetworkCredential credentials) : base(communityUrl, credentials) { }

/// <summary>
/// Cast a vote on the specified idea, replacing any previous vote by the requesting person. The incoming JSON must include a boolean "promote" field
/// that is true if the requestor is promoting this idea, or false if the requestor is demoting it.
/// Cast a vote on the specified idea, replacing any previous vote by the included voter. The incoming JSON
/// must include a boolean "promote" field and an int "id" field within a People object named "voter" that
/// is true if the voter is promoting this idea, or false if the voter is demoting it. This requires elevated
/// permissions if the requester does not match the voter.
/// </summary>
/// <param name="contentID">The ID of the content object for which to cast a "promote" vote</param>
/// <param name="idea_vote">The vote entity containing the promote field (assumed to be true if not present)</param>
/// <param name="contentID">The ID of the content object for which to cast an idea vote</param>
/// <param name="idea_vote">The vote entity containing the promote field where true is promoting and false is demoting (assumed to be false if not present), and the voter's id within the voter field</param>
public void CreateVote(int contentID, IdeaVote idea_vote)
{
string url = ideaVotesUrl;
url += "/" + contentID.ToString();

string json = JsonConvert.SerializeObject(idea_vote, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
// Jive only looks at the authorization when creating the vote, not the voter passed in the json.
// Because of this, we use RunAs() and forget about most of the json.
// This entire function couldn't be run in a RunAs because it doesn't return anything and Func<T>
// must have a return type.
string json = "{\"promote\":" + (idea_vote.promote ? "true" : "false") + "}";
try
{
PostAbsolute(url, json);
RunAs(idea_vote.voter.id, () => PostAbsolute(url, json));
}
catch (HttpException e)
{
Expand All @@ -47,6 +53,10 @@ public void CreateVote(int contentID, IdeaVote idea_vote)
throw new HttpException(e.WebEventCode, "You attempted to vote on an issue for which voting has been disabled", e);
case 410:
throw new HttpException(e.WebEventCode, "If this Jive instance is not licensed for the Ideation module", e);
case 500:
throw new HttpException(e.WebEventCode, "If there was an internal server error", e);
default:
throw;
}
}

Expand All @@ -65,7 +75,7 @@ public List<IdeaVote> GetVotes(int contentID, int count = 25, int startIndex = 0
{
List<IdeaVote> voteList = new List<IdeaVote>();

//construcs the correct url based on the user's specifications
//constructs the correct url based on the user's specifications
string url = ideaVotesUrl;
url += "/" + contentID.ToString();
url += "?count=" + (count > 1000 ? 1000 : count).ToString();
Expand Down
31 changes: 27 additions & 4 deletions Net.Pokeshot.JiveSdk/Clients/JiveClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public abstract class JiveClient
private readonly NetworkCredential _credential;
protected string JiveCommunityUrl;
private int? _imposter = null;
private bool _secondTry = false;
private static int _numGets = 0;
private static int _numPosts = 0;
private static int _numPuts = 0;
Expand Down Expand Up @@ -76,9 +77,17 @@ public static void ResetApiCounters()
public T RunAs<T>(int personId, Func<T> method)
{
_imposter = personId;
var returnVal = method();
T returnVal = default(T);
try
{
returnVal = method();
}
catch (Exception ex)
{
_imposter = null;
throw;
}
_imposter = null;

return returnVal;
}

Expand Down Expand Up @@ -115,7 +124,21 @@ protected string GetAbsolute(string url, string authorization)
requestMessage.Headers.Add("X-Jive-Run-As", "userid " + _imposter);
}

HttpResponseMessage activityResponse = httpClient.SendAsync(requestMessage).Result;
HttpResponseMessage activityResponse;
try
{
activityResponse = httpClient.SendAsync(requestMessage).Result;
}
catch (Exception e)
{
// If timed out, try once more.
if (!_secondTry)
{
_secondTry = true;
return GetAbsolute(url, authorization);
}
else throw;
}

if (!activityResponse.IsSuccessStatusCode)
{
Expand Down Expand Up @@ -319,7 +342,7 @@ protected string DeleteAbsolute(string url)

protected string jiveDateFormat(DateTime time)
{
return time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff") + "%2B0000";
return time.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff") + "Z";
}
}
}
86 changes: 80 additions & 6 deletions Net.Pokeshot.JiveSdk/Clients/PeopleClient.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using Net.Pokeshot.JiveSdk.Models;
using System.Web;
Expand Down Expand Up @@ -100,7 +97,7 @@ public Person FindPersonByEmail(string email)
{
try
{
person = GetPersonByUsername("[email protected]");
person = GetPersonByEmail("[email protected]");
}
catch (HttpException)
{
Expand Down Expand Up @@ -1665,7 +1662,7 @@ public Person GetPersonByEmail(string email, List<string> fields = null)
}
catch (HttpException e)
{
Console.WriteLine(e.Message);
//Console.WriteLine(e.Message);
switch (e.GetHttpCode())
{
case 400:
Expand Down Expand Up @@ -1797,7 +1794,84 @@ public List<Stream> GetStreams(int personID, List<string> fields = null)
//GetReports()
//GetResources()
//GetRoles()
//GetSecurityGroups()

/// <summary>
/// Return a list of SecurityGroups that the specified user is a member of. Note that this list will NOT include any security groups that this person is an administrator of. Because the number of security groups will generally be very small, pagination is not supported.
/// </summary>
/// <param name="personID">ID of the user for whom to return security groups</param>
/// <param name="fields">Fields to be returned</param>
/// <returns></returns>
public List<SecurityGroup> GetSecurityGroups(int personID, List<string> fields = null)
{
string url = peopleUrl + "/" + personID + "/securityGroups";
if (fields != null && fields.Count > 0)
{
url += "?fields=";
foreach (var field in fields)
{
url += field + ",";
}
// remove last comma
url = url.Remove(url.Length - 1);
}
string json;
try
{
json = GetAbsolute(url);
}
catch (HttpException e)
{
Console.WriteLine(e.Message);
switch (e.GetHttpCode())
{
case 403:
throw new HttpException(e.WebEventCode, "Requester is not allowed to view security groups for the owning user", e);
case 404:
throw new HttpException(e.WebEventCode, "Specified user cannot be found", e);
default:
throw;
}
}

JObject results = JObject.Parse(json);

return results["list"].ToObject<List<SecurityGroup>>();
}

/// <summary>
/// Update the specified user based on the contents of updatedPerson. Only modifiable fields that actually provide a value in the incoming entity are processed
/// </summary>
/// <param name="updatedPerson">Updated person</param>
/// <returns>Person object reflecting the processed changes</returns>
public Person Update(Person updatedPerson)
{
string url = peopleUrl + "/" + updatedPerson.id;
string json = JsonConvert.SerializeObject(updatedPerson, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore, Formatting = Formatting.Indented });
try
{
json = PutAbsolute(url, json);
}
catch (HttpException e)
{
Console.WriteLine(e.Message);
switch (e.GetHttpCode())
{
case 400:
throw new HttpException(e.WebEventCode, "One or more of the input fields is malformed", e);
case 403:
throw new HttpException(e.WebEventCode, "Requesting user is not authorized to make changes to the specified user", e);
case 404:
throw new HttpException(e.WebEventCode, "Specified user does not exist", e);
case 409:
throw new HttpException(e.WebEventCode, "Requested change would cause business rules to be violated (such as more than one user with the same email address)", e);
default:
throw;
}
}

return JObject.Parse(json).ToObject<Person>();
}

//GetSocialUsers()
//GetSupportedFields()
//GetTagsUserTaggedOnUser()
Expand Down
2 changes: 1 addition & 1 deletion Net.Pokeshot.JiveSdk/Clients/PlacesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public GenericContent CreateContent(int placeID, GenericContent content, DateTim
case 403:
throw new HttpException(e.WebEventCode, "You are not allowed to access the specified content or place", e);
case 409:
throw new HttpException(e.WebEventCode, "The new entity would conflict with system restrictions (such as two contents of the same type with the same name", e);
throw new HttpException(e.WebEventCode, "The new entity would conflict with system restrictions (such as two contents of the same type with the same name) or would post content more than once every 90 seconds", e);
default:
throw;
}
Expand Down
40 changes: 39 additions & 1 deletion Net.Pokeshot.JiveSdk/Clients/RsvpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,49 @@
using System.Text;
using System.Threading.Tasks;
using System.Net;
using Net.Pokeshot.JiveSdk.Models;
using System.Web;

namespace Net.Pokeshot.JiveSdk.Clients
{
class RsvpClient : JiveClient
public class RsvpClient : JiveClient
{
string rsvpUrl { get { return JiveCommunityUrl + "/api/core/ext/event-type-plugin/v3/rsvp"; } }

public RsvpClient(string communityUrl, NetworkCredential credentials) : base(communityUrl, credentials) { }

public string Create(int contentID, Person person, RsvpResponse response)
{
string json = "" + (int) response;
string url = rsvpUrl + "/" + contentID.ToString();
string result;

try
{
result = RunAs(person.id, () => PostAbsolute(url, json));
}
catch (HttpException e)
{
switch (e.GetHttpCode())
{
case 400:
throw new HttpException(e.WebEventCode, "An input field is missing or malformed", e);
case 403:
throw new HttpException(e.WebEventCode, "You are not allowed to perform this operation", e);
case 404:
throw new HttpException(e.WebEventCode, "The specified parent content object (or comment) cannot be found", e);
default:
throw;
}
}
return result;
}
}

public enum RsvpResponse
{
Yes = 1,
No = 2,
Maybe = 3
}
}
Loading