diff --git a/src/Agni.Social/Agni.Social.csproj b/src/Agni.Social/Agni.Social.csproj
new file mode 100644
index 0000000..3635410
--- /dev/null
+++ b/src/Agni.Social/Agni.Social.csproj
@@ -0,0 +1,51 @@
+
+
+
+ netstandard2.0
+ Agni OS Social Network Assembly
+
+
+
+ ..\..\out\Debug\
+ ..\..\out\Debug\Agni.Social.xml
+ true
+
+
+
+ ..\..\out\Release\
+ ..\..\out\Release\Agni.Social.xml
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\lib\nfx\NFX.dll
+
+
+ ..\lib\nfx\NFX.Wave.dll
+
+
+ ..\lib\nfx\NFX.Web.dll
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Agni.Social/Exceptions.cs b/src/Agni.Social/Exceptions.cs
new file mode 100644
index 0000000..8704587
--- /dev/null
+++ b/src/Agni.Social/Exceptions.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+using NFX;
+
+namespace Agni.Social
+{
+ ///
+ /// Base exception thrown by the social framework
+ ///
+ [Serializable]
+ public class SocialException : AgniException
+ {
+ public SocialException() : base() { }
+ public SocialException(int code) : base(code) { }
+ public SocialException(int code, string message) : base(code, message) { }
+ public SocialException(string message) : base(message) { }
+ public SocialException(string message, Exception inner) : base(message, inner) { }
+ public SocialException(string message, Exception inner, int code, string sender, string topic) : base(message, inner, code, sender, topic) { }
+ protected SocialException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/src/Agni.Social/Graph/Client/GraphCommentManager.cs b/src/Agni.Social/Graph/Client/GraphCommentManager.cs
new file mode 100644
index 0000000..91039d4
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphCommentManager.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Agni.Coordination;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ public sealed class GraphCommentManager : GraphCommentManagerBase
+ {
+ public GraphCommentManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override Comment Create(GDID gAuthorNode, GDID gTargetNode, string dimension, string content, byte[] data,
+ PublicationState publicationState, RatingValue rating = RatingValue.Undefined, DateTime? epoch = null)
+ {
+ var pair = HostSet.AssignHost(gTargetNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Create(gAuthorNode, gTargetNode, dimension, content, data, publicationState, rating, epoch),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override Comment Respond(GDID gAuthorNode, CommentID parent, string content, byte[] data)
+ {
+ var pair = HostSet.AssignHost(parent.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Respond(gAuthorNode, parent, content, data),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override GraphChangeStatus Update(CommentID ratingId, RatingValue value, string content, byte[] data)
+ {
+ var pair = HostSet.AssignHost(ratingId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Update(ratingId, value, content, data),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override GraphChangeStatus DeleteComment(CommentID commentId)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.DeleteComment(commentId),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override GraphChangeStatus Like(CommentID commentId, int deltaLike, int deltaDislike)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Like(commentId, deltaLike, deltaDislike),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.IsCommentedByAuthor(gNode, gAuthor, dimension),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override IEnumerable GetNodeSummaries(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(
+ commentSystem => commentSystem.GetNodeSummaries(gNode),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override IEnumerable Fetch(CommentQuery query)
+ {
+ var pair = HostSet.AssignHost(query.G_TargetNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(
+ commentSystem => commentSystem.Fetch(query),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override IEnumerable FetchResponses(CommentID commentId)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(
+ commentSystem => commentSystem.FetchResponses(commentId),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override IEnumerable FetchComplaints(CommentID commentId)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(
+ commentSystem => commentSystem.FetchComplaints(commentId),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override Comment GetComment(CommentID commentId)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.GetComment(commentId),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message)
+ {
+ var pair = HostSet.AssignHost(commentId.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Complain(commentId, gAuthorNode, kind, message),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+
+ public override GraphChangeStatus Justify(CommentID commentID)
+ {
+ var pair = HostSet.AssignHost(commentID.G_Volume);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(
+ commentSystem => commentSystem.Justify(commentID),
+ pair.Select(host => host.RegionPath)
+ );
+ }
+ }
+ ///
+ /// Заглушка для интерфейса IGraphCommentSystem на клиенте
+ ///
+ public sealed class NOPGraphCommentManager : GraphCommentManagerBase
+ {
+ public NOPGraphCommentManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override Comment Create(GDID gAuthorNode, GDID gTargetNode, string dimension, string content, byte[] data,
+ PublicationState publicationState, RatingValue rating = RatingValue.Undefined, DateTime? epoch = null)
+ {
+ return default(Comment);
+ }
+
+ public override Comment Respond(GDID gAuthorNode, CommentID parent, string content, byte[] data)
+ {
+ return default(Comment);
+ }
+
+ public override GraphChangeStatus Update(CommentID ratingId, RatingValue value, string content, byte[] data)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus DeleteComment(CommentID commentId)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus Like(CommentID commentId, int deltaLike, int deltaDislike)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension)
+ {
+ return false;
+ }
+
+ public override IEnumerable GetNodeSummaries(GDID gNode)
+ {
+ yield break;
+ }
+
+ public override IEnumerable Fetch(CommentQuery query)
+ {
+ yield break;
+ }
+
+ public override IEnumerable FetchResponses(CommentID commentId)
+ {
+ yield break;
+ }
+
+ public override IEnumerable FetchComplaints(CommentID commentId)
+ {
+ yield break;
+ }
+
+ public override Comment GetComment(CommentID commentId)
+ {
+ return default(Comment);
+ }
+
+ public override GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus Justify(CommentID commentID)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Client/GraphEventManager.cs b/src/Agni.Social/Graph/Client/GraphEventManager.cs
new file mode 100644
index 0000000..3b00bf2
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphEventManager.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Agni.Coordination;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ public sealed class GraphEventManager : GraphEventManagerBase
+ {
+ public GraphEventManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override void EmitEvent(Event evt)
+ {
+ var pair = HostSet.AssignHost(evt.G_EmitterNode);
+ Contracts.ServiceClientHub
+ .CallWithRetry(eventSystem => eventSystem.EmitEvent(evt), pair.Select(host => host.RegionPath));
+ }
+
+ public override void Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters)
+ {
+ var pair = HostSet.AssignHost(gEmitterNode);
+ Contracts.ServiceClientHub
+ .CallWithRetry(eventSystem => eventSystem.Subscribe(gRecipientNode, gEmitterNode, parameters), pair.Select(host => host.RegionPath));
+ }
+
+ public override void Unsubscribe(GDID gRecipientNode, GDID gEmitterNode)
+ {
+ var pair = HostSet.AssignHost(gEmitterNode);
+ Contracts.ServiceClientHub
+ .CallWithRetry(eventSystem => eventSystem.Unsubscribe(gRecipientNode, gEmitterNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override long EstimateSubscriberCount(GDID gEmitterNode)
+ {
+ var pair = HostSet.AssignHost(gEmitterNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(eventSystem => eventSystem.EstimateSubscriberCount(gEmitterNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override IEnumerable GetSubscribers(GDID gEmitterNode, long start, int count)
+ {
+ var pair = HostSet.AssignHost(gEmitterNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(eventSystem => eventSystem.GetSubscribers(gEmitterNode, start, count), pair.Select(host => host.RegionPath));
+ }
+ }
+
+ ///
+ /// Заглушка для интерфейса IGraphEventSystem на клиенте
+ ///
+
+ public sealed class NOPGraphEventManager : GraphEventManagerBase
+ {
+ public NOPGraphEventManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override void EmitEvent(Event evt)
+ {
+ }
+
+ public override void Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters)
+ {
+ }
+
+ public override void Unsubscribe(GDID gRecipientNode, GDID gEmitterNode)
+ {
+ }
+
+ public override long EstimateSubscriberCount(GDID gEmitterNode)
+ {
+ return 0;
+ }
+
+ public override IEnumerable GetSubscribers(GDID gEmitterNode, long start, int count)
+ {
+ yield break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Client/GraphFriendManager.cs b/src/Agni.Social/Graph/Client/GraphFriendManager.cs
new file mode 100644
index 0000000..3544892
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphFriendManager.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Agni.Coordination;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ public class GraphFriendManager : GraphFriendManagerBase
+ {
+ public GraphFriendManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override IEnumerable GetFriendLists(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(friendSystem => friendSystem.GetFriendLists(gNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus AddFriendList(GDID gNode, string list, string description)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(friendSystem => friendSystem.AddFriendList(gNode, list, description), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus DeleteFriendList(GDID gNode, string list)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(friendSystem => friendSystem.DeleteFriendList(gNode, list), pair.Select(host => host.RegionPath));
+ }
+
+ public override IEnumerable GetFriendConnections(FriendQuery query)
+ {
+ var pair = HostSet.AssignHost(query.G_Node);
+ return Contracts.ServiceClientHub
+ .CallWithRetry>(friendSystem => friendSystem.GetFriendConnections(query), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus AddFriend(GDID gNode, GDID gFriendNode, bool? approve)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(friendSystem => friendSystem.AddFriend(gNode, gFriendNode, approve), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus AssignFriendLists(GDID gNode, GDID gFriendNode, string lists)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(friendSystem => friendSystem.AssignFriendLists(gNode, gFriendNode, lists), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus DeleteFriend(GDID gNode, GDID gFriendNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub
+ .CallWithRetry(friendSystem => friendSystem.DeleteFriend(gNode, gFriendNode), pair.Select(host => host.RegionPath));
+ }
+ }
+
+ ///
+ /// Заглушка для интерфейса IGraphFriendSystem на клиенте
+ ///
+ public class NOPGraphFriendManager : GraphFriendManagerBase
+ {
+ public NOPGraphFriendManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override IEnumerable GetFriendLists(GDID gNode)
+ {
+ yield break;
+ }
+
+ public override GraphChangeStatus AddFriendList(GDID gNode, string list, string description)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus DeleteFriendList(GDID gNode, string list)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override IEnumerable GetFriendConnections(FriendQuery query)
+ {
+ yield break;
+ }
+
+ public override GraphChangeStatus AddFriend(GDID gNode, GDID gFriendNode, bool? approve)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus AssignFriendLists(GDID gNode, GDID gFriendNode, string lists)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus DeleteFriend(GDID gNode, GDID gFriendNode)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Client/GraphManager.cs b/src/Agni.Social/Graph/Client/GraphManager.cs
new file mode 100644
index 0000000..143c31a
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphManager.cs
@@ -0,0 +1,115 @@
+using System;
+using Agni.Social.Graph.Server;
+using Agni.WebMessaging;
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Фасад для клиента, для работы с IGraphNodeSystem, IGraphCommentSystem, IGraphEventSystem, IGraphFriendSystem
+ ///
+ public sealed class GraphManager : DisposableObject, IApplicationStarter, IApplicationFinishNotifiable
+ {
+ #region ctor
+ private static object s_Lock = new object();
+ private static volatile GraphManager s_Instance;
+
+ public static GraphManager Instance
+ {
+ get
+ {
+ var instance = s_Instance;
+ if (instance == null)
+ throw new GraphException(StringConsts.GS_INSTANCE_DATA_LAYER_IS_NOT_ALLOCATED_ERROR.Args(typeof(GraphSystemService).Name));
+ return instance;
+ }
+ }
+
+ private GraphManager()
+ {
+ lock (s_Lock)
+ {
+ if (s_Instance != null)
+ throw new GraphException(StringConsts.GS_INSTANCE_ALREADY_ALLOCATED_ERROR.Args(GetType().Name));
+ s_Instance = this;
+ }
+ }
+
+ protected override void Destructor()
+ {
+ lock (s_Lock)
+ {
+ base.Destructor();
+ s_Instance = null;
+ }
+ }
+
+ #endregion
+
+ #region fields
+
+ private IConfigSectionNode m_Config;
+
+ private GraphNodeManagerBase m_Nodes;
+ private GraphCommentManagerBase m_Comments;
+ private GraphEventManagerBase m_Events;
+ private GraphFriendManagerBase m_Friends;
+
+ #endregion
+
+ #region properties
+
+ public IGraphNodeSystem Nodes {get { return m_Nodes; }}
+ public IGraphCommentSystem Comments {get { return m_Comments; }}
+ public IGraphEventSystem Events { get { return m_Events; }}
+ public IGraphFriendSystem Friends {get { return m_Friends; }}
+
+ public bool ApplicationStartBreakOnException {get { return true; } }
+ public string Name { get { return GetType().Name; } }
+
+
+ #endregion
+
+ public void Configure(IConfigSectionNode node)
+ {
+ m_Config = node;
+ }
+
+ public void ApplicationStartBeforeInit(IApplication application)
+ {
+ }
+ ///
+ /// Не все клиенты могут быть сконфигурированы, если не сконфигурированы, то ставим заглушки
+ ///
+ public void ApplicationStartAfterInit(IApplication application)
+ {
+ var nodeHostSetAttr = m_Config.AttrByName(SocialConsts.CONFIG_GRAPH_NODE_HOST_SET_ATTR).Value;
+ var commentHostSetAttr = m_Config.AttrByName(SocialConsts.CONFIG_GRAPH_COMMENT_HOST_SET_ATTR).Value;
+ var eventHostSetAttr = m_Config.AttrByName(SocialConsts.CONFIG_GRAPH_EVENT_HOST_SET_ATTR).Value;
+ var friendHostSetAttr = m_Config.AttrByName(SocialConsts.CONFIG_GRAPH_FRIEND_HOST_SET_ATTR).Value;
+
+
+ m_Nodes = nodeHostSetAttr != null ? (GraphNodeManagerBase) new GraphNodeManager(AgniSystem.ProcessManager.HostSets[nodeHostSetAttr]) : new NOPGraphNodeManager(null);
+ m_Comments = commentHostSetAttr != null
+ ? (GraphCommentManagerBase) new GraphCommentManager(AgniSystem.ProcessManager.HostSets[commentHostSetAttr])
+ : new NOPGraphCommentManager(null);
+ m_Events = eventHostSetAttr != null ? (GraphEventManagerBase) new GraphEventManager(AgniSystem.ProcessManager.HostSets[eventHostSetAttr]) : new NOPGraphEventManager(null);
+ m_Friends = friendHostSetAttr != null ? (GraphFriendManagerBase) new GraphFriendManager(AgniSystem.ProcessManager.HostSets[friendHostSetAttr]) : new NOPGraphFriendManager(null);
+
+ }
+
+ public void ApplicationFinishBeforeCleanup(IApplication application)
+ {
+ DisposableObject.DisposeAndNull(ref m_Nodes);
+ DisposableObject.DisposeAndNull(ref m_Comments);
+ DisposableObject.DisposeAndNull(ref m_Events);
+ DisposableObject.DisposeAndNull(ref m_Friends);
+ }
+
+ public void ApplicationFinishAfterCleanup(IApplication application)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Client/GraphManagerBase.cs b/src/Agni.Social/Graph/Client/GraphManagerBase.cs
new file mode 100644
index 0000000..e4a42f7
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphManagerBase.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using Agni.Coordination;
+using NFX;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Базовый класс для всех интерфейсов клиентов
+ /// Для "подмешивания" IDisposible
+ ///
+ public class GraphManagerBase : DisposableObject, IDisposable
+ {
+ public GraphManagerBase(HostSet hostSet)
+ {
+ m_HostSet = hostSet;
+ }
+
+ protected HostSet m_HostSet;
+
+ public HostSet HostSet
+ {
+ get
+ {
+ return m_HostSet;
+ }
+ }
+
+ protected override void Destructor()
+ {
+ DisposeAndNull(ref m_HostSet);
+ base.Destructor();
+ }
+ }
+
+ public abstract class GraphNodeManagerBase : GraphManagerBase, IGraphNodeSystem
+ {
+ public GraphNodeManagerBase(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public abstract GraphChangeStatus SaveNode(GraphNode node);
+ public abstract GraphNode GetNode(GDID gNode);
+ public abstract GraphChangeStatus DeleteNode(GDID gNode);
+ public abstract GraphChangeStatus UndeleteNode(GDID gNode);
+ public abstract GraphChangeStatus RemoveNode(GDID gNode);
+ }
+
+ public abstract class GraphCommentManagerBase : GraphManagerBase, IGraphCommentSystem
+ {
+ public GraphCommentManagerBase(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public abstract Comment Create(GDID gAuthorNode, GDID gTargetNode, string dimension, string content, byte[] data,
+ PublicationState publicationState, RatingValue rating = RatingValue.Undefined, DateTime? epoch = null);
+
+ public abstract Comment Respond(GDID gAuthorNode, CommentID parent, string content, byte[] data);
+
+ public abstract GraphChangeStatus Update(CommentID ratingId, RatingValue value, string content, byte[] data);
+
+ public abstract GraphChangeStatus DeleteComment(CommentID commentId);
+
+ public abstract GraphChangeStatus Like(CommentID commentId, int deltaLike, int deltaDislike);
+
+ public abstract bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension);
+
+ public abstract IEnumerable GetNodeSummaries(GDID gNode);
+
+ public abstract IEnumerable Fetch(CommentQuery query);
+
+ public abstract IEnumerable FetchResponses(CommentID commentId);
+
+ public abstract IEnumerable FetchComplaints(CommentID commentId);
+
+ public abstract Comment GetComment(CommentID commentId);
+
+ public abstract GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message);
+
+ public abstract GraphChangeStatus Justify(CommentID commentId);
+ }
+
+ public abstract class GraphEventManagerBase : GraphManagerBase, IGraphEventSystem
+ {
+ public GraphEventManagerBase(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public abstract void EmitEvent(Event evt);
+
+ public abstract void Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters);
+
+ public abstract void Unsubscribe(GDID gRecipientNode, GDID gEmitterNode);
+
+ public abstract long EstimateSubscriberCount(GDID gEmitterNode);
+
+ public abstract IEnumerable GetSubscribers(GDID gEmitterNode, long start, int count);
+ }
+
+ public abstract class GraphFriendManagerBase : GraphManagerBase, IGraphFriendSystem
+ {
+ public GraphFriendManagerBase(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public abstract IEnumerable GetFriendLists(GDID gNode);
+
+ public abstract GraphChangeStatus AddFriendList(GDID gNode, string list, string description);
+
+ public abstract GraphChangeStatus DeleteFriendList(GDID gNode, string list);
+
+ public abstract IEnumerable GetFriendConnections(FriendQuery query);
+
+ public abstract GraphChangeStatus AddFriend(GDID gNode, GDID gFriendNode, bool? approve);
+
+ public abstract GraphChangeStatus AssignFriendLists(GDID gNode, GDID gFriendNode, string lists);
+
+ public abstract GraphChangeStatus DeleteFriend(GDID gNode, GDID gFriendNode);
+ }
+
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Client/GraphNodeManager.cs b/src/Agni.Social/Graph/Client/GraphNodeManager.cs
new file mode 100644
index 0000000..1601be6
--- /dev/null
+++ b/src/Agni.Social/Graph/Client/GraphNodeManager.cs
@@ -0,0 +1,86 @@
+using System.Linq;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.DataAccess.Distributed;
+using NFX.Environment;
+
+using Agni.Coordination;
+
+namespace Agni.Social.Graph
+{
+ public sealed class GraphNodeManager : GraphNodeManagerBase
+ {
+
+ public GraphNodeManager(HostSet hostSet) : base(hostSet)
+ {
+
+ }
+
+ public override GraphChangeStatus SaveNode(GraphNode node)
+ {
+ var pair = HostSet.AssignHost(node.GDID);
+ return Contracts.ServiceClientHub.CallWithRetry(nodeSystem => nodeSystem.SaveNode(node), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphNode GetNode(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub.CallWithRetry(nodeSystem => nodeSystem.GetNode(gNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus DeleteNode(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub.CallWithRetry(nodeSystem => nodeSystem.DeleteNode(gNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus UndeleteNode(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub.CallWithRetry(nodeSystem => nodeSystem.UndeleteNode(gNode), pair.Select(host => host.RegionPath));
+ }
+
+ public override GraphChangeStatus RemoveNode(GDID gNode)
+ {
+ var pair = HostSet.AssignHost(gNode);
+ return Contracts.ServiceClientHub.CallWithRetry(nodeSystem => nodeSystem.RemoveNode(gNode), pair.Select(host => host.RegionPath));
+ }
+
+ }
+
+ ///
+ /// Заглушка для интерфейса IGraphNodeSystem на клиенте
+ ///
+ public sealed class NOPGraphNodeManager : GraphNodeManagerBase
+ {
+ public NOPGraphNodeManager(HostSet hostSet) : base(hostSet)
+ {
+ }
+
+ public override GraphChangeStatus SaveNode(GraphNode node)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphNode GetNode(GDID gNode)
+ {
+ return default(GraphNode);
+ }
+
+ public override GraphChangeStatus DeleteNode(GDID gNode)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus UndeleteNode(GDID gNode)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+
+ public override GraphChangeStatus RemoveNode(GDID gNode)
+ {
+ return GraphChangeStatus.NotFound;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/CommentID.cs b/src/Agni.Social/Graph/CommentID.cs
new file mode 100644
index 0000000..7f8649d
--- /dev/null
+++ b/src/Agni.Social/Graph/CommentID.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using NFX;
+using NFX.DataAccess.Distributed;
+using NFX.Serialization.JSON;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Represents a read-only tuple of { gVolume: GDID, gComment: GDID}.
+ /// The gRating is a globally-unique ID however graph system prepends it with
+ /// gVolume which allows for instant location of a concrete data store which holds gRating.
+ ///
+ [Serializable]
+ public struct CommentID : IEquatable, IJSONWritable
+ {
+ public CommentID(GDID gVolume, GDID gComment) { G_Volume = gVolume; G_Comment = gComment; }
+
+ ///
+ /// Sharding GDID used to instantly find the data store shard where data is kept
+ ///
+ public readonly GDID G_Volume;
+
+ ///
+ ///The global unique id of a comment
+ ///
+ public readonly GDID G_Comment;
+
+ ///
+ /// True if struct is unassigned
+ ///
+ public bool Unassigned { get{return G_Volume.IsZero;} }
+
+ ///
+ /// True if G_Volume | G_Comment isZero
+ ///
+ public bool IsZero
+ {
+ get { return Unassigned || G_Comment.IsZero; }
+ }
+
+ public bool Equals(CommentID other)
+ {
+ return this.G_Volume == other.G_Volume &&
+ this.G_Comment == other.G_Comment;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is CommentID)) return false;
+ return this.Equals((CommentID)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return G_Volume.GetHashCode() ^ G_Comment.GetHashCode();
+ }
+
+ public string Stringify()
+ {
+ var eLink = new ELink(G_Volume, G_Comment.Bytes);
+ return eLink.Link;
+ }
+
+ public override string ToString()
+ {
+ return "Comment [{0}@{1}]".Args(G_Volume, G_Comment);
+ }
+
+ public void WriteAsJSON(TextWriter wri, int nestingLevel, JSONWritingOptions options = null)
+ {
+ wri.Write('"');
+ wri.Write(Stringify());
+ wri.Write('"');
+ }
+
+ public static CommentID Parse(string str)
+ {
+ CommentID result;
+ if (!TryParse(str, out result))
+ throw new SocialException("CommentID.Parse({0})".Args(str));
+
+ return result;
+ }
+
+ public static bool TryParse(string str, out CommentID commentId)
+ {
+ try
+ {
+ var eLink = new ELink(str);
+ var gVolume = eLink.GDID;
+ var gComment = new GDID(eLink.Metadata);
+ commentId = new CommentID(gVolume, gComment);
+ return true;
+ }
+ catch
+ {
+ commentId = default(CommentID);
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/DTO/Comment.cs b/src/Agni.Social/Graph/DTO/Comment.cs
new file mode 100644
index 0000000..8fcda0f
--- /dev/null
+++ b/src/Agni.Social/Graph/DTO/Comment.cs
@@ -0,0 +1,106 @@
+using System;
+using NFX;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Contains social comment data
+ ///
+ [Serializable]
+ public struct Comment
+ {
+ ///
+ /// Comment
+ ///
+ /// ID
+ /// Parent comment ID
+ /// Author node
+ /// Target node
+ /// Creation Date
+ /// Scope of comments
+ /// Publication state
+ /// Rating (0,1,2,3,4,5)
+ /// Message
+ /// Data
+ /// Likes count
+ /// Dislikes count
+ /// Complaint count
+ /// Response count
+ /// In use (InUse = false - comment has been deleted)
+ /// Can be edited
+ public Comment(CommentID id,
+ CommentID? parentId,
+ GraphNode authorNode,
+ GraphNode targetNode,
+ DateTime createDate,
+ string dimension,
+
+ PublicationState publicationState,
+ RatingValue rating,
+ string message,
+ byte[] data,
+
+ uint likes,
+ uint dislikes,
+ uint complaintCount,
+ uint responseCount,
+
+ bool inUse,
+ bool editable)
+ {
+ ID = id;
+ ParentID = parentId;
+ AuthorNode = authorNode;
+ TargetNode = targetNode;
+ Create_Date = createDate;
+ Dimension = dimension;
+
+ PublicationState = publicationState;
+ Rating = rating;
+ Message = message;
+ Data = data;
+
+ Likes = likes;
+ Dislikes = dislikes;
+ ComplaintCount = complaintCount;
+ ResponseCount = responseCount;
+
+ IsRoot = !parentId.HasValue;
+ In_Use = inUse;
+ Editable = editable;
+ }
+
+ public readonly CommentID ID;
+ public readonly CommentID? ParentID;
+ public readonly GraphNode AuthorNode;
+ public readonly GraphNode TargetNode;
+ public readonly DateTime Create_Date;
+
+ public readonly bool IsRoot;
+ public readonly RatingValue Rating;
+ public readonly string Message;
+ public readonly byte[] Data;
+
+ public readonly uint Likes;
+ public readonly uint Dislikes;
+ public readonly uint ComplaintCount;
+ public readonly PublicationState PublicationState;
+ public readonly bool In_Use;
+ public readonly string Dimension;
+ public readonly bool Editable;
+ public readonly uint ResponseCount;
+
+ public override string ToString()
+ {
+ return "[{0}-{1}]; [{2}] - {3}; {4}; {5}; ({6}) {7}; Like - {8}; Dislike - {9}".Args(ID.G_Volume, ID.G_Comment, AuthorNode.GDID, AuthorNode.OriginName, TargetNode.GDID, Create_Date, Rating, Message, Likes, Dislikes);
+ }
+
+ ///
+ /// Make new Comment
+ ///
+ internal static Comment MakeNew(CommentID commentID, CommentID? parentID, GraphNode authorNode, GraphNode targetNode, DateTime create_Date, string dimension, PublicationState publicationState, RatingValue rating, string message, byte[] data)
+ {
+ return new Comment(commentID, parentID, authorNode, targetNode, create_Date, dimension, publicationState, rating, message, data, 0, 0, 0, 0, true, true);
+ }
+ }
+}
diff --git a/src/Agni.Social/Graph/DTO/Complaint.cs b/src/Agni.Social/Graph/DTO/Complaint.cs
new file mode 100644
index 0000000..220cb03
--- /dev/null
+++ b/src/Agni.Social/Graph/DTO/Complaint.cs
@@ -0,0 +1,44 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Contains social comment data
+ ///
+ [Serializable]
+ public struct Complaint
+ {
+ public Complaint(CommentID commentID,
+ GDID gComplaint,
+ GraphNode authorNode,
+ string kind,
+ string message,
+ DateTime createDate,
+ bool inUse)
+ {
+ CommentID = commentID;
+ GDID = gComplaint;
+ AuthorNode = authorNode;
+ Kind = kind;
+ Message = message;
+ Create_Date = createDate;
+ In_Use = inUse;
+ }
+
+ public readonly CommentID CommentID;
+ public readonly GDID GDID;
+ public readonly GraphNode AuthorNode;
+ public readonly string Kind;
+ public readonly string Message;
+ public readonly DateTime Create_Date;
+ public readonly bool In_Use;
+
+ public override string ToString()
+ {
+ return "[{0}-{1}-{2}]: {3}-{4} by {5} ({6})".Args(CommentID.G_Volume, CommentID.G_Comment, GDID, Kind, Message, AuthorNode.OriginName, AuthorNode.GDID);
+ }
+ }
+}
diff --git a/src/Agni.Social/Graph/DTO/SummaryRating.cs b/src/Agni.Social/Graph/DTO/SummaryRating.cs
new file mode 100644
index 0000000..1f9bf0e
--- /dev/null
+++ b/src/Agni.Social/Graph/DTO/SummaryRating.cs
@@ -0,0 +1,43 @@
+using System;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Contains summarized rating information per graph node
+ ///
+ [Serializable]
+ public struct SummaryRating
+ {
+ internal SummaryRating(GDID gNode, DateTime createDate, DateTime lastDate, string dimension, ulong count, ulong rating1, ulong rating2, ulong rating3, ulong rating4, ulong rating5)
+ {
+ G_Node = gNode;
+ CreateDate = createDate;
+ LastChangeDate = lastDate;
+ Dimension = dimension;
+ Count = count;
+ Rating1 = rating1;
+ Rating2 = rating2;
+ Rating3 = rating3;
+ Rating4 = rating4;
+ Rating5 = rating5;
+ TotalRatings = Rating1 + Rating2 + Rating3 + Rating4 + Rating5;
+
+ var tr = (float)TotalRatings;
+ Rating = tr > 0f ? (Rating1 * 1 + Rating2 * 2 + Rating3 * 3 + Rating4 * 4 + Rating5 * 5) / tr : 0f;
+ }
+
+ public readonly GDID G_Node;
+ public readonly DateTime CreateDate;
+ public readonly DateTime LastChangeDate;
+ public readonly string Dimension;
+ public readonly ulong Count;
+ public readonly ulong Rating1;
+ public readonly ulong Rating2;
+ public readonly ulong Rating3;
+ public readonly ulong Rating4;
+ public readonly ulong Rating5;
+ public readonly ulong TotalRatings;
+ public readonly float Rating;
+ }
+}
diff --git a/src/Agni.Social/Graph/Enums.cs b/src/Agni.Social/Graph/Enums.cs
new file mode 100644
index 0000000..e01f374
--- /dev/null
+++ b/src/Agni.Social/Graph/Enums.cs
@@ -0,0 +1,108 @@
+namespace Agni.Social.Graph
+{
+ ///
+ /// Denotes the results of graph changing operations, such as: Create, Update, Delete
+ ///
+ public enum GraphChangeStatus
+ {
+ NotFound = -1,
+ Unassigned = 0,
+ Added,
+ Updated,
+ Deleted
+ }
+
+ ///
+ /// Denotes friendship request direction - who requested the friendship (and who approved)
+ ///
+ public enum FriendshipRequestDirection
+ {
+ ///
+ /// I requested = G_GraphNode
+ ///
+ I = 0,
+
+ ///
+ /// Friend requested = G_FriendNode
+ ///
+ F = 1, Friend = F
+ }
+
+ ///
+ /// Denotes friend statuses
+ ///
+ public enum FriendStatusFilter
+ {
+ Approved = 0,
+ PendingApproval,
+ Banned,
+ All
+ }
+
+ ///
+ /// Friend approval status
+ ///
+ public enum FriendStatus
+ {
+ Pending = 0,
+ Approved,
+ Denied,
+ Banned
+ }
+
+ public enum FriendVisibility
+ {
+ ///
+ /// Anyone can see friends, even non-logged users/internet/google
+ ///
+ Anyone = 0,
+
+ ///
+ /// Only logged-in users may see friends
+ ///
+ Public,
+
+ ///
+ /// Only firends can see user's friends
+ ///
+ Friends,
+
+ ///
+ /// Only users can see their own friends
+ ///
+ Private
+ }
+
+ ///
+ /// Defines rating grades
+ ///
+ public enum RatingValue
+ {
+ Undefined = 0,
+ Star1 = 1,
+ Star2 = 2,
+ Star3 = 3,
+ Star4 = 4,
+ Star5 = 5
+ }
+
+
+ public enum PublicationState
+ {
+ //todo Нужны состояния публикации
+ Private = 0,
+ Public,
+ Friend,
+ Deleted
+ }
+
+ public enum CommentOrderType
+ {
+ ByDate,
+ ByPositive,
+ ByNegative,
+ ByPopular,
+ ByUsefull
+ }
+
+}
diff --git a/src/Agni.Social/Graph/Event.cs b/src/Agni.Social/Graph/Event.cs
new file mode 100644
index 0000000..da44f1f
--- /dev/null
+++ b/src/Agni.Social/Graph/Event.cs
@@ -0,0 +1,57 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Represents and event that is emitted into the GraphEventSystem
+ ///
+ [Serializable]
+ public struct Event
+ {
+ ///
+ /// Creates a new GraphNode with new GDID
+ ///
+ public static Event MakeNew(GDID gEmitterNode, string eType, GDID gTargetShard, GDID gTarget, string config)
+ {
+ //todo pereipsat na imnovaniy gdid
+ var gdid = GDID.Zero;//AgniSystem.GDIDProvider.GenerateOneGDID(SysConsts.GDID_NS_SOCIAL, SysConsts.GDID_NAME_SOCIAL_EVENT);
+ var utcNow = App.TimeSource.UTCNow;
+ return new Event(gdid, utcNow, gEmitterNode, eType, gTargetShard, gTarget, config);
+ }
+
+ internal Event(GDID gdid, DateTime utcTimestamp, GDID gEmitterNode, string eType, GDID gTargetShard, GDID gTarget, string config)
+ {
+ GDID = gdid;
+ TimestampUTC = utcTimestamp;
+ G_EmitterNode = gEmitterNode;
+ EventType = eType;
+ G_TargetShard = gTargetShard;
+ G_Target = gTarget;
+ Config = config;
+ }
+
+ /// Unique ID of the event itself
+ public readonly GDID GDID;
+
+ /// Event creation UTC timestamp
+ public readonly DateTime TimestampUTC;
+
+ /// GDID of the emitter node
+ public readonly GDID G_EmitterNode;
+
+ /// Event type per particular business system
+ public readonly string EventType;
+
+ /// The sharding gdid of the target (such as a GDID of the user where post is stored)
+ public readonly GDID G_TargetShard;
+
+ /// The GDID of the target that this event represents, (such as the GDID of the post on user's wall)
+ public readonly GDID G_Target;
+
+ /// Arbitrary parameters in Laconic format
+ public readonly string Config;
+ }
+}
diff --git a/src/Agni.Social/Graph/Exceptions.cs b/src/Agni.Social/Graph/Exceptions.cs
new file mode 100644
index 0000000..6978ef8
--- /dev/null
+++ b/src/Agni.Social/Graph/Exceptions.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+using NFX;
+
+namespace Agni.Social
+{
+ ///
+ /// Base exception thrown by the social Graph framework
+ ///
+ [Serializable]
+ public class GraphException : SocialException
+ {
+ public GraphException() : base() { }
+ public GraphException(int code) : base(code) { }
+ public GraphException(int code, string message) : base(code, message) { }
+ public GraphException(string message) : base(message) { }
+ public GraphException(string message, Exception inner) : base(message, inner) { }
+ public GraphException(string message, Exception inner, int code, string sender, string topic) : base(message, inner, code, sender, topic) { }
+ protected GraphException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/src/Agni.Social/Graph/FriendConnection.cs b/src/Agni.Social/Graph/FriendConnection.cs
new file mode 100644
index 0000000..7233538
--- /dev/null
+++ b/src/Agni.Social/Graph/FriendConnection.cs
@@ -0,0 +1,37 @@
+using System;
+
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Returns data about a connected friend
+ ///
+ public struct FriendConnection
+ {
+ public FriendConnection(GraphNode friend,
+ DateTime requestDate,
+ DateTime? approveDate,
+ FriendshipRequestDirection dir,
+ FriendVisibility visibility,
+ string groups)
+ {
+ Friend = friend;
+ RequestDate = requestDate;
+ ApproveDate = approveDate;
+ Direction = dir;
+ Visibility = visibility;
+ Groups = groups;
+ }
+
+ public readonly GraphNode Friend;
+ public readonly DateTime RequestDate;
+ public readonly DateTime? ApproveDate;
+ public readonly FriendshipRequestDirection Direction;
+ public readonly FriendVisibility Visibility;
+
+ /// A comma-delimited list of friend group ids
+ public readonly string Groups;
+
+ public bool Approved { get { return ApproveDate.HasValue; } }
+ }
+}
diff --git a/src/Agni.Social/Graph/GraphCommentSystemClient.cs b/src/Agni.Social/Graph/GraphCommentSystemClient.cs
new file mode 100644
index 0000000..7010409
--- /dev/null
+++ b/src/Agni.Social/Graph/GraphCommentSystemClient.cs
@@ -0,0 +1,446 @@
+//Generated by Agni.Clients.Tools.AgniGluecCompiler
+
+/* Auto generated by Glue Client Compiler tool (gluec)
+on 12/11/2017 10:45:22 PM at LAPCHENKO_DESK by User
+Do not modify this file by hand if you plan to regenerate this file again by the tool as manual changes will be lost
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using NFX.Glue;
+using NFX.Glue.Protocol;
+
+
+namespace Agni.Social.Graph
+{
+// This implementation needs @Agni.@Social.@Graph.@IGraphCommentSystemClient, so
+// it can be used with ServiceClientHub class
+
+ ///
+ /// Client for glued contract Agni.Social.Graph.IGraphCommentSystem server.
+ /// Each contract method has synchronous and asynchronous versions, the later denoted by 'Async_' prefix.
+ /// May inject client-level inspectors here like so:
+ /// client.MsgInspectors.Register( new YOUR_CLIENT_INSPECTOR_TYPE());
+ ///
+ public class GraphCommentSystemClient : ClientEndPoint, @Agni.@Social.@Graph.@IGraphCommentSystemClient
+ {
+
+ #region Static Members
+
+ private static TypeSpec s_ts_CONTRACT;
+ private static MethodSpec @s_ms_Create_0;
+ private static MethodSpec s_ms_Respond_1;
+ private static MethodSpec @s_ms_Update_2;
+ private static MethodSpec @s_ms_DeleteComment_3;
+ private static MethodSpec @s_ms_Like_4;
+ private static MethodSpec @s_ms_IsCommentedByAuthor_5;
+ private static MethodSpec @s_ms_GetNodeSummaries_6;
+ private static MethodSpec @s_ms_Fetch_7;
+ private static MethodSpec @s_ms_FetchResponses_8;
+ private static MethodSpec @s_ms_FetchComplaints_9;
+ private static MethodSpec @s_ms_GetComment_10;
+ private static MethodSpec @s_ms_Complain_11;
+ private static MethodSpec @s_ms_Justify_12;
+
+ //static .ctor
+ static GraphCommentSystemClient()
+ {
+ var t = typeof(@Agni.@Social.@Graph.@IGraphCommentSystem);
+ s_ts_CONTRACT = new TypeSpec(t);
+ @s_ms_Create_0 = new MethodSpec(t.GetMethod("Create", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String), typeof(@System.@String), typeof(@System.@Byte[]), typeof(@Agni.@Social.@Graph.@PublicationState), typeof(@Agni.@Social.@Graph.@RatingValue), typeof(@System.@Nullable<@System.@DateTime>) }));
+ @s_ms_Respond_1 = new MethodSpec(t.GetMethod("Respond", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@Agni.@Social.@Graph.@CommentID), typeof(@System.@String), typeof(@System.@Byte[]) }));
+ @s_ms_Update_2 = new MethodSpec(t.GetMethod("Update", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID), typeof(@Agni.@Social.@Graph.@RatingValue), typeof(@System.@String), typeof(@System.@Byte[]) }));
+ @s_ms_DeleteComment_3 = new MethodSpec(t.GetMethod("DeleteComment", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID) }));
+ @s_ms_Like_4 = new MethodSpec(t.GetMethod("Like", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID), typeof(@System.@Int32), typeof(@System.@Int32) }));
+ @s_ms_IsCommentedByAuthor_5 = new MethodSpec(t.GetMethod("IsCommentedByAuthor", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String) }));
+ @s_ms_GetNodeSummaries_6 = new MethodSpec(t.GetMethod("GetNodeSummaries", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_Fetch_7 = new MethodSpec(t.GetMethod("Fetch", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentQuery) }));
+ @s_ms_FetchResponses_8 = new MethodSpec(t.GetMethod("FetchResponses", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID) }));
+ @s_ms_FetchComplaints_9 = new MethodSpec(t.GetMethod("FetchComplaints", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID) }));
+ @s_ms_GetComment_10 = new MethodSpec(t.GetMethod("GetComment", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID) }));
+ @s_ms_Complain_11 = new MethodSpec(t.GetMethod("Complain", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String), typeof(@System.@String) }));
+ @s_ms_Justify_12 = new MethodSpec(t.GetMethod("Justify", new Type[]{ typeof(@Agni.@Social.@Graph.@CommentID) }));
+ }
+ #endregion
+
+ #region .ctor
+ public GraphCommentSystemClient(string node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphCommentSystemClient(Node node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphCommentSystemClient(IGlue glue, string node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+ public GraphCommentSystemClient(IGlue glue, Node node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+
+ //common instance .ctor body
+ private void ctor()
+ {
+
+ }
+
+ #endregion
+
+ public override Type Contract
+ {
+ get { return typeof(@Agni.@Social.@Graph.@IGraphCommentSystem); }
+ }
+
+
+
+ #region Contract Methods
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Create'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@Comment' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@Comment @Create(@NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @NFX.@DataAccess.@Distributed.@GDID @gTargetNode, @System.@String @dimension, @System.@String @content, @System.@Byte[] @data, @Agni.@Social.@Graph.@PublicationState @publicationState, @Agni.@Social.@Graph.@RatingValue @value, @System.@Nullable<@System.@DateTime> @timeStamp)
+ {
+ var call = Async_Create(@gAuthorNode, @gTargetNode, @dimension, @content, @data, @publicationState, @value, @timeStamp);
+ return call.GetValue<@Agni.@Social.@Graph.@Comment>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Create'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Create(@NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @NFX.@DataAccess.@Distributed.@GDID @gTargetNode, @System.@String @dimension, @System.@String @content, @System.@Byte[] @data, @Agni.@Social.@Graph.@PublicationState @publicationState, @Agni.@Social.@Graph.@RatingValue @value, @System.@Nullable<@System.@DateTime> @timeStamp)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Create_0, false, RemoteInstance, new object[]{@gAuthorNode, @gTargetNode, @dimension, @content, @data, @publicationState, @value, @timeStamp});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Response'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@Comment' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@Comment Respond(@NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @Agni.@Social.@Graph.@CommentID @parent, @System.@String @content, @System.@Byte[] @data)
+ {
+ var call = Async_Respond(@gAuthorNode, @parent, @content, @data);
+ return call.GetValue<@Agni.@Social.@Graph.@Comment>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Response'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Respond(@NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @Agni.@Social.@Graph.@CommentID @parent, @System.@String @content, @System.@Byte[] @data)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, s_ms_Respond_1, false, RemoteInstance, new object[]{@gAuthorNode, @parent, @content, @data});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Update'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @Update(@Agni.@Social.@Graph.@CommentID @ratingId, @Agni.@Social.@Graph.@RatingValue @value, @System.@String @content, @System.@Byte[] @data)
+ {
+ var call = Async_Update(@ratingId, @value, @content, @data);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Update'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Update(@Agni.@Social.@Graph.@CommentID @ratingId, @Agni.@Social.@Graph.@RatingValue @value, @System.@String @content, @System.@Byte[] @data)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Update_2, false, RemoteInstance, new object[]{@ratingId, @value, @content, @data});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.DeleteComment'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @DeleteComment(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var call = Async_DeleteComment(@commentId);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.DeleteComment'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_DeleteComment(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_DeleteComment_3, false, RemoteInstance, new object[]{@commentId});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Like'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @Like(@Agni.@Social.@Graph.@CommentID @commentId, @System.@Int32 @deltaLike, @System.@Int32 @deltaDislike)
+ {
+ var call = Async_Like(@commentId, @deltaLike, @deltaDislike);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Like'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Like(@Agni.@Social.@Graph.@CommentID @commentId, @System.@Int32 @deltaLike, @System.@Int32 @deltaDislike)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Like_4, false, RemoteInstance, new object[]{@commentId, @deltaLike, @deltaDislike});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.IsCommentedByAuthor'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Boolean' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Boolean @IsCommentedByAuthor(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gAuthor, @System.@String @dimension)
+ {
+ var call = Async_IsCommentedByAuthor(@gNode, @gAuthor, @dimension);
+ return call.GetValue<@System.@Boolean>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.IsCommentedByAuthor'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_IsCommentedByAuthor(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gAuthor, @System.@String @dimension)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_IsCommentedByAuthor_5, false, RemoteInstance, new object[]{@gNode, @gAuthor, @dimension});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.GetNodeSummaries'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@SummaryRating>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@SummaryRating> @GetNodeSummaries(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_GetNodeSummaries(@gNode);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@SummaryRating>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.GetNodeSummaries'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetNodeSummaries(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetNodeSummaries_6, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Fetch'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment> @Fetch(@Agni.@Social.@Graph.@CommentQuery @query)
+ {
+ var call = Async_Fetch(@query);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Fetch'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Fetch(@Agni.@Social.@Graph.@CommentQuery @query)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Fetch_7, false, RemoteInstance, new object[]{@query});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.FetchResponses'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment> @FetchResponses(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var call = Async_FetchResponses(@commentId);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Comment>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.FetchResponses'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_FetchResponses(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_FetchResponses_8, false, RemoteInstance, new object[]{@commentId});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.FetchComplaints'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Complaint>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Complaint> @FetchComplaints(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var call = Async_FetchComplaints(@commentId);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@Complaint>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.FetchComplaints'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_FetchComplaints(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_FetchComplaints_9, false, RemoteInstance, new object[]{@commentId});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.GetComment'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@Comment' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@Comment @GetComment(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var call = Async_GetComment(@commentId);
+ return call.GetValue<@Agni.@Social.@Graph.@Comment>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.GetComment'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetComment(@Agni.@Social.@Graph.@CommentID @commentId)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetComment_10, false, RemoteInstance, new object[]{@commentId});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Complain'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @Complain(@Agni.@Social.@Graph.@CommentID @commentId, @NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @System.@String @kind, @System.@String @message)
+ {
+ var call = Async_Complain(@commentId, @gAuthorNode, @kind, @message);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Complain'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Complain(@Agni.@Social.@Graph.@CommentID @commentId, @NFX.@DataAccess.@Distributed.@GDID @gAuthorNode, @System.@String @kind, @System.@String @message)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Complain_11, false, RemoteInstance, new object[]{@commentId, @gAuthorNode, @kind, @message});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Justify'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @Justify(@Agni.@Social.@Graph.@CommentID @commentID)
+ {
+ var call = Async_Justify(@commentID);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphCommentSystem.Justify'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Justify(@Agni.@Social.@Graph.@CommentID @commentID)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Justify_12, false, RemoteInstance, new object[]{@commentID});
+ return DispatchCall(request);
+ }
+
+
+ #endregion
+
+ }//class
+}//namespace
diff --git a/src/Agni.Social/Graph/GraphEventSystemClient.cs b/src/Agni.Social/Graph/GraphEventSystemClient.cs
new file mode 100644
index 0000000..0594df3
--- /dev/null
+++ b/src/Agni.Social/Graph/GraphEventSystemClient.cs
@@ -0,0 +1,214 @@
+//Generated by Agni.Clients.Tools.AgniGluecCompiler
+
+/* Auto generated by Glue Client Compiler tool (gluec)
+on 22.08.2017 19:41:45 at CRAZYROGUE by mad
+Do not modify this file by hand if you plan to regenerate this file again by the tool as manual changes will be lost
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using NFX.Glue;
+using NFX.Glue.Protocol;
+
+
+namespace Agni.Social.Graph
+{
+// This implementation needs @Agni.@Social.@Graph.@IGraphEventSystemClient, so
+// it can be used with ServiceClientHub class
+
+ ///
+ /// Client for glued contract Agni.Social.Graph.IGraphEventSystem server.
+ /// Each contract method has synchronous and asynchronous versions, the later denoted by 'Async_' prefix.
+ /// May inject client-level inspectors here like so:
+ /// client.MsgInspectors.Register( new YOUR_CLIENT_INSPECTOR_TYPE());
+ ///
+ public class GraphEventSystemClient : ClientEndPoint, @Agni.@Social.@Graph.@IGraphEventSystemClient
+ {
+
+ #region Static Members
+
+ private static TypeSpec s_ts_CONTRACT;
+ private static MethodSpec @s_ms_EmitEvent_0;
+ private static MethodSpec @s_ms_Subscribe_1;
+ private static MethodSpec @s_ms_Unsubscribe_2;
+ private static MethodSpec @s_ms_EstimateSubscriberCount_3;
+ private static MethodSpec @s_ms_GetSubscribers_4;
+
+ //static .ctor
+ static GraphEventSystemClient()
+ {
+ var t = typeof(@Agni.@Social.@Graph.@IGraphEventSystem);
+ s_ts_CONTRACT = new TypeSpec(t);
+ @s_ms_EmitEvent_0 = new MethodSpec(t.GetMethod("EmitEvent", new Type[]{ typeof(@Agni.@Social.@Graph.@Event) }));
+ @s_ms_Subscribe_1 = new MethodSpec(t.GetMethod("Subscribe", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@Byte[]) }));
+ @s_ms_Unsubscribe_2 = new MethodSpec(t.GetMethod("Unsubscribe", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_EstimateSubscriberCount_3 = new MethodSpec(t.GetMethod("EstimateSubscriberCount", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_GetSubscribers_4 = new MethodSpec(t.GetMethod("GetSubscribers", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@Int64), typeof(@System.@Int32) }));
+ }
+ #endregion
+
+ #region .ctor
+ public GraphEventSystemClient(string node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphEventSystemClient(Node node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphEventSystemClient(IGlue glue, string node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+ public GraphEventSystemClient(IGlue glue, Node node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+
+ //common instance .ctor body
+ private void ctor()
+ {
+
+ }
+
+ #endregion
+
+ public override Type Contract
+ {
+ get { return typeof(@Agni.@Social.@Graph.@IGraphEventSystem); }
+ }
+
+
+
+ #region Contract Methods
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.EmitEvent'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public void @EmitEvent(@Agni.@Social.@Graph.@Event @evt)
+ {
+ var call = Async_EmitEvent(@evt);
+ call.CheckVoidValue();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.EmitEvent'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_EmitEvent(@Agni.@Social.@Graph.@Event @evt)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_EmitEvent_0, false, RemoteInstance, new object[]{@evt});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.Subscribe'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public void @Subscribe(@NFX.@DataAccess.@Distributed.@GDID @gRecipientNode, @NFX.@DataAccess.@Distributed.@GDID @gEmitterNode, @System.@Byte[] @parameters)
+ {
+ var call = Async_Subscribe(@gRecipientNode, @gEmitterNode, @parameters);
+ call.CheckVoidValue();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.Subscribe'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Subscribe(@NFX.@DataAccess.@Distributed.@GDID @gRecipientNode, @NFX.@DataAccess.@Distributed.@GDID @gEmitterNode, @System.@Byte[] @parameters)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Subscribe_1, false, RemoteInstance, new object[]{@gRecipientNode, @gEmitterNode, @parameters});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.Unsubscribe'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public void @Unsubscribe(@NFX.@DataAccess.@Distributed.@GDID @gRecipientNode, @NFX.@DataAccess.@Distributed.@GDID @gEmitterNode)
+ {
+ var call = Async_Unsubscribe(@gRecipientNode, @gEmitterNode);
+ call.CheckVoidValue();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.Unsubscribe'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_Unsubscribe(@NFX.@DataAccess.@Distributed.@GDID @gRecipientNode, @NFX.@DataAccess.@Distributed.@GDID @gEmitterNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_Unsubscribe_2, false, RemoteInstance, new object[]{@gRecipientNode, @gEmitterNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.EstimateSubscriberCount'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Int64' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Int64 @EstimateSubscriberCount(@NFX.@DataAccess.@Distributed.@GDID @gEmitterNode)
+ {
+ var call = Async_EstimateSubscriberCount(@gEmitterNode);
+ return call.GetValue<@System.@Int64>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.EstimateSubscriberCount'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_EstimateSubscriberCount(@NFX.@DataAccess.@Distributed.@GDID @gEmitterNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_EstimateSubscriberCount_3, false, RemoteInstance, new object[]{@gEmitterNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.GetSubscribers'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@GraphNode>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@GraphNode> @GetSubscribers(@NFX.@DataAccess.@Distributed.@GDID @gEmitterNode, @System.@Int64 @start, @System.@Int32 @count)
+ {
+ var call = Async_GetSubscribers(@gEmitterNode, @start, @count);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@GraphNode>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphEventSystem.GetSubscribers'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetSubscribers(@NFX.@DataAccess.@Distributed.@GDID @gEmitterNode, @System.@Int64 @start, @System.@Int32 @count)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetSubscribers_4, false, RemoteInstance, new object[]{@gEmitterNode, @start, @count});
+ return DispatchCall(request);
+ }
+
+
+ #endregion
+
+ }//class
+}//namespace
diff --git a/src/Agni.Social/Graph/GraphFriendSystemClient.cs b/src/Agni.Social/Graph/GraphFriendSystemClient.cs
new file mode 100644
index 0000000..1ba137e
--- /dev/null
+++ b/src/Agni.Social/Graph/GraphFriendSystemClient.cs
@@ -0,0 +1,272 @@
+//Generated by Agni.Clients.Tools.AgniGluecCompiler
+
+/* Auto generated by Glue Client Compiler tool (gluec)
+on 8/13/2017 3:50:49 PM at SEXTOD by Anton
+Do not modify this file by hand if you plan to regenerate this file again by the tool as manual changes will be lost
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using NFX.Glue;
+using NFX.Glue.Protocol;
+
+
+namespace Agni.Social.Graph
+{
+// This implementation needs @Agni.@Social.@Graph.@IGraphFriendSystemClient, so
+// it can be used with ServiceClientHub class
+
+ ///
+ /// Client for glued contract Agni.Social.Graph.IGraphFriendSystem server.
+ /// Each contract method has synchronous and asynchronous versions, the later denoted by 'Async_' prefix.
+ /// May inject client-level inspectors here like so:
+ /// client.MsgInspectors.Register( new YOUR_CLIENT_INSPECTOR_TYPE());
+ ///
+ public class GraphFriendSystemClient : ClientEndPoint, @Agni.@Social.@Graph.@IGraphFriendSystemClient
+ {
+
+ #region Static Members
+
+ private static TypeSpec s_ts_CONTRACT;
+ private static MethodSpec @s_ms_GetFriendLists_0;
+ private static MethodSpec @s_ms_AddFriendList_1;
+ private static MethodSpec @s_ms_DeleteFriendList_2;
+ private static MethodSpec @s_ms_GetFriendConnections_3;
+ private static MethodSpec @s_ms_AddFriend_4;
+ private static MethodSpec @s_ms_AssignFriendLists_5;
+ private static MethodSpec @s_ms_DeleteFriend_6;
+
+ //static .ctor
+ static GraphFriendSystemClient()
+ {
+ var t = typeof(@Agni.@Social.@Graph.@IGraphFriendSystem);
+ s_ts_CONTRACT = new TypeSpec(t);
+ @s_ms_GetFriendLists_0 = new MethodSpec(t.GetMethod("GetFriendLists", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_AddFriendList_1 = new MethodSpec(t.GetMethod("AddFriendList", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String), typeof(@System.@String) }));
+ @s_ms_DeleteFriendList_2 = new MethodSpec(t.GetMethod("DeleteFriendList", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String) }));
+ @s_ms_GetFriendConnections_3 = new MethodSpec(t.GetMethod("GetFriendConnections", new Type[]{ typeof(@Agni.@Social.@Graph.@FriendQuery) }));
+ @s_ms_AddFriend_4 = new MethodSpec(t.GetMethod("AddFriend", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@Nullable<@System.@Boolean>) }));
+ @s_ms_AssignFriendLists_5 = new MethodSpec(t.GetMethod("AssignFriendLists", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@System.@String) }));
+ @s_ms_DeleteFriend_6 = new MethodSpec(t.GetMethod("DeleteFriend", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID), typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ }
+ #endregion
+
+ #region .ctor
+ public GraphFriendSystemClient(string node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphFriendSystemClient(Node node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphFriendSystemClient(IGlue glue, string node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+ public GraphFriendSystemClient(IGlue glue, Node node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+
+ //common instance .ctor body
+ private void ctor()
+ {
+
+ }
+
+ #endregion
+
+ public override Type Contract
+ {
+ get { return typeof(@Agni.@Social.@Graph.@IGraphFriendSystem); }
+ }
+
+
+
+ #region Contract Methods
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.GetFriendLists'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@System.@String>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@System.@String> @GetFriendLists(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_GetFriendLists(@gNode);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@System.@String>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.GetFriendLists'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetFriendLists(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetFriendLists_0, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AddFriendList'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @AddFriendList(@NFX.@DataAccess.@Distributed.@GDID @gNode, @System.@String @list, @System.@String @description)
+ {
+ var call = Async_AddFriendList(@gNode, @list, @description);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AddFriendList'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_AddFriendList(@NFX.@DataAccess.@Distributed.@GDID @gNode, @System.@String @list, @System.@String @description)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_AddFriendList_1, false, RemoteInstance, new object[]{@gNode, @list, @description});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.DeleteFriendList'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @DeleteFriendList(@NFX.@DataAccess.@Distributed.@GDID @gNode, @System.@String @list)
+ {
+ var call = Async_DeleteFriendList(@gNode, @list);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.DeleteFriendList'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_DeleteFriendList(@NFX.@DataAccess.@Distributed.@GDID @gNode, @System.@String @list)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_DeleteFriendList_2, false, RemoteInstance, new object[]{@gNode, @list});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.GetFriendConnections'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@FriendConnection>' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@FriendConnection> @GetFriendConnections(@Agni.@Social.@Graph.@FriendQuery @query)
+ {
+ var call = Async_GetFriendConnections(@query);
+ return call.GetValue<@System.@Collections.@Generic.@IEnumerable<@Agni.@Social.@Graph.@FriendConnection>>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.GetFriendConnections'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetFriendConnections(@Agni.@Social.@Graph.@FriendQuery @query)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetFriendConnections_3, false, RemoteInstance, new object[]{@query});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AddFriend'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @AddFriend(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode, @System.@Nullable<@System.@Boolean> @approve)
+ {
+ var call = Async_AddFriend(@gNode, @gFriendNode, @approve);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AddFriend'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_AddFriend(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode, @System.@Nullable<@System.@Boolean> @approve)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_AddFriend_4, false, RemoteInstance, new object[]{@gNode, @gFriendNode, @approve});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AssignFriendLists'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @AssignFriendLists(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode, @System.@String @lists)
+ {
+ var call = Async_AssignFriendLists(@gNode, @gFriendNode, @lists);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.AssignFriendLists'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_AssignFriendLists(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode, @System.@String @lists)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_AssignFriendLists_5, false, RemoteInstance, new object[]{@gNode, @gFriendNode, @lists});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.DeleteFriend'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @DeleteFriend(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode)
+ {
+ var call = Async_DeleteFriend(@gNode, @gFriendNode);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphFriendSystem.DeleteFriend'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_DeleteFriend(@NFX.@DataAccess.@Distributed.@GDID @gNode, @NFX.@DataAccess.@Distributed.@GDID @gFriendNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_DeleteFriend_6, false, RemoteInstance, new object[]{@gNode, @gFriendNode});
+ return DispatchCall(request);
+ }
+
+
+ #endregion
+
+ }//class
+}//namespace
diff --git a/src/Agni.Social/Graph/GraphNode.cs b/src/Agni.Social/Graph/GraphNode.cs
new file mode 100644
index 0000000..5465f52
--- /dev/null
+++ b/src/Agni.Social/Graph/GraphNode.cs
@@ -0,0 +1,108 @@
+using NFX.DataAccess.Distributed;
+using System;
+using Agni.Social.Graph.Server.Data;
+using NFX;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Contains data about the node of the social graph
+ ///
+ [Serializable]
+ public struct GraphNode
+ {
+
+ ///
+ /// Creates a new GraphNode with new GDID
+ ///
+ public static GraphNode MakeNew(string nodeType, GDID gOrigShard, GDID gOrig, string origName, byte[] origData, FriendVisibility defaultFriendVisibility)
+ {
+ var gdid = NodeRow.GenerateNewNodeRowGDID();
+ var utcNow = App.TimeSource.UTCNow;
+ return new GraphNode(nodeType, gdid, gOrigShard, gOrig, origName, origData, utcNow, defaultFriendVisibility);
+ }
+
+ public static GraphNode Copy(GraphNode from,
+ string origName,
+ byte[] originData,
+ DateTime utcTimestamp,
+ FriendVisibility defaultFriendVisibility)
+ {
+ return new GraphNode(from.NodeType, from.GDID, from.G_OriginShard, from.G_Origin, origName ?? from.OriginName, originData ?? from.OriginData, utcTimestamp, from.DefaultFriendVisibility);
+ }
+
+ ///
+ /// Creates GraphNode, when GDID is assigned the graph system tries to update existing node by GDID, otheriwse
+ /// creates a new node with the specified GDID. Use MakeNew() to generate new GDID
+ ///
+ public GraphNode(string nodeType,
+ GDID gdid,
+ GDID gOrigShard,
+ GDID gOrig,
+ string origName,
+ byte[] originData,
+ DateTime utcTimestamp,
+ FriendVisibility defaultFriendVisibility)
+ {
+ if (nodeType.IsNullOrWhiteSpace() || nodeType.Length>Server.Data.Schema.GSNodeType.MAX_LEN)
+ throw new GraphException(StringConsts.ARGUMENT_ERROR+"GraphNode.ctor(nodeType=null|>{0})".Args(Server.Data.Schema.GSNodeType.MAX_LEN));
+
+ NodeType = nodeType;
+ GDID = gdid;
+ G_OriginShard = gOrigShard;
+ G_Origin = gOrig;
+ OriginName = origName;
+ OriginData = originData;
+ TimestampUTC = utcTimestamp;
+ DefaultFriendVisibility = defaultFriendVisibility;
+ }
+
+ ///
+ /// Returns true if this struct is not assigned any value
+ ///
+ public bool Unassigned { get{ return NodeType==null;} }
+
+ ///
+ /// This value depends on the origin database types, i.e.: User, Organization, Forum...
+ ///
+ public readonly string NodeType;
+
+ ///
+ /// Graph Node GDID, unique through different types.
+ /// The GraphSystem makes this GDID and returns back to the origin for stamping, so
+ /// original entity may find this GraphNode by this GDID
+ ///
+ public readonly GDID GDID;
+
+ /// The sharding GDID in the business origin database, (i.e. G_USER - sharding key)
+ public readonly GDID G_OriginShard;
+
+ ///
+ /// The GDID of the the entoty of NodeType in the business origin database,
+ /// (e.g. G_USERCAR (if the car is kept in user's shard)) - this may or may not be the same as G_OriginShard
+ ///
+ public readonly GDID G_Origin;
+ ///
+ /// Description, e.g. for user - screen name
+ ///
+ public readonly string OriginName;
+ ///
+ /// Data about node - BSON
+ ///
+ public readonly byte[] OriginData;
+ ///
+ /// Creation timestamp
+ ///
+ public readonly DateTime TimestampUTC;
+ ///
+ /// Determines the default setting of who can see friends of this nodes
+ ///
+ public readonly FriendVisibility DefaultFriendVisibility;
+
+ public override string ToString()
+ {
+ return "GN({0}, {1}, '{2}')".Args(GDID, NodeType, OriginName);
+ }
+
+ }
+}
diff --git a/src/Agni.Social/Graph/GraphNodeSystemClient.cs b/src/Agni.Social/Graph/GraphNodeSystemClient.cs
new file mode 100644
index 0000000..2300423
--- /dev/null
+++ b/src/Agni.Social/Graph/GraphNodeSystemClient.cs
@@ -0,0 +1,214 @@
+//Generated by Agni.Clients.Tools.AgniGluecCompiler
+
+/* Auto generated by Glue Client Compiler tool (gluec)
+on 08.09.2017 18:36:32 at CRAZYROGUE by mad
+Do not modify this file by hand if you plan to regenerate this file again by the tool as manual changes will be lost
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using NFX.Glue;
+using NFX.Glue.Protocol;
+
+
+namespace Agni.Social.Graph
+{
+// This implementation needs @Agni.@Social.@Graph.@IGraphNodeSystemClient, so
+// it can be used with ServiceClientHub class
+
+ ///
+ /// Client for glued contract Agni.Social.Graph.IGraphNodeSystem server.
+ /// Each contract method has synchronous and asynchronous versions, the later denoted by 'Async_' prefix.
+ /// May inject client-level inspectors here like so:
+ /// client.MsgInspectors.Register( new YOUR_CLIENT_INSPECTOR_TYPE());
+ ///
+ public class GraphNodeSystemClient : ClientEndPoint, @Agni.@Social.@Graph.@IGraphNodeSystemClient
+ {
+
+ #region Static Members
+
+ private static TypeSpec s_ts_CONTRACT;
+ private static MethodSpec @s_ms_SaveNode_0;
+ private static MethodSpec @s_ms_GetNode_1;
+ private static MethodSpec @s_ms_DeleteNode_2;
+ private static MethodSpec @s_ms_UndeleteNode_3;
+ private static MethodSpec @s_ms_RemoveNode_4;
+
+ //static .ctor
+ static GraphNodeSystemClient()
+ {
+ var t = typeof(@Agni.@Social.@Graph.@IGraphNodeSystem);
+ s_ts_CONTRACT = new TypeSpec(t);
+ @s_ms_SaveNode_0 = new MethodSpec(t.GetMethod("SaveNode", new Type[]{ typeof(@Agni.@Social.@Graph.@GraphNode) }));
+ @s_ms_GetNode_1 = new MethodSpec(t.GetMethod("GetNode", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_DeleteNode_2 = new MethodSpec(t.GetMethod("DeleteNode", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_UndeleteNode_3 = new MethodSpec(t.GetMethod("UndeleteNode", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ @s_ms_RemoveNode_4 = new MethodSpec(t.GetMethod("RemoveNode", new Type[]{ typeof(@NFX.@DataAccess.@Distributed.@GDID) }));
+ }
+ #endregion
+
+ #region .ctor
+ public GraphNodeSystemClient(string node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphNodeSystemClient(Node node, Binding binding = null) : base(node, binding) { ctor(); }
+ public GraphNodeSystemClient(IGlue glue, string node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+ public GraphNodeSystemClient(IGlue glue, Node node, Binding binding = null) : base(glue, node, binding) { ctor(); }
+
+ //common instance .ctor body
+ private void ctor()
+ {
+
+ }
+
+ #endregion
+
+ public override Type Contract
+ {
+ get { return typeof(@Agni.@Social.@Graph.@IGraphNodeSystem); }
+ }
+
+
+
+ #region Contract Methods
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.SaveNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @SaveNode(@Agni.@Social.@Graph.@GraphNode @node)
+ {
+ var call = Async_SaveNode(@node);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.SaveNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_SaveNode(@Agni.@Social.@Graph.@GraphNode @node)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_SaveNode_0, false, RemoteInstance, new object[]{@node});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.GetNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphNode' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphNode @GetNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_GetNode(@gNode);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphNode>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.GetNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_GetNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_GetNode_1, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.DeleteNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @DeleteNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_DeleteNode(@gNode);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.DeleteNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_DeleteNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_DeleteNode_2, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.UndeleteNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @UndeleteNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_UndeleteNode(@gNode);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.UndeleteNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_UndeleteNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_UndeleteNode_3, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+
+ ///
+ /// Synchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.RemoveNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning '@Agni.@Social.@Graph.@GraphChangeStatus' or WrappedExceptionData instance.
+ /// ClientCallException is thrown if the call could not be placed in the outgoing queue.
+ /// RemoteException is thrown if the server generated exception during method execution.
+ ///
+ public @Agni.@Social.@Graph.@GraphChangeStatus @RemoveNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var call = Async_RemoveNode(@gNode);
+ return call.GetValue<@Agni.@Social.@Graph.@GraphChangeStatus>();
+ }
+
+ ///
+ /// Asynchronous invoker for 'Agni.Social.Graph.IGraphNodeSystem.RemoveNode'.
+ /// This is a two-way call per contract specification, meaning - the server sends the result back either
+ /// returning no exception or WrappedExceptionData instance.
+ /// CallSlot is returned that can be queried for CallStatus, ResponseMsg and result.
+ ///
+ public CallSlot Async_RemoveNode(@NFX.@DataAccess.@Distributed.@GDID @gNode)
+ {
+ var request = new RequestAnyMsg(s_ts_CONTRACT, @s_ms_RemoveNode_4, false, RemoteInstance, new object[]{@gNode});
+ return DispatchCall(request);
+ }
+
+
+ #endregion
+
+ }//class
+}//namespace
diff --git a/src/Agni.Social/Graph/IGraphCommentSystem.cs b/src/Agni.Social/Graph/IGraphCommentSystem.cs
new file mode 100644
index 0000000..909be9b
--- /dev/null
+++ b/src/Agni.Social/Graph/IGraphCommentSystem.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+
+
+using NFX.DataAccess.Distributed;
+using NFX.Glue;
+
+using Agni.Contracts;
+
+namespace Agni.Social.Graph
+{
+ [Glued]
+ [LifeCycle(ServerInstanceMode.Singleton)]
+ public interface IGraphCommentSystem : IAgniService
+ {
+ ///
+ /// Create new comment with rating
+ ///
+ /// GDID of autor node
+ /// GDID of target node
+ /// Scope for rating
+ /// Content
+ /// Byte array of data
+ /// State of publication
+ /// star 1-2-3-4-5
+ /// Time of current action
+ /// Comment
+ Comment Create(GDID gAuthorNode,
+ GDID gTargetNode,
+ string dimension,
+ string content,
+ byte[] data,
+ PublicationState publicationState,
+ RatingValue value = RatingValue.Undefined,
+ DateTime? timeStamp = null);
+
+ ///
+ /// Make response to target commentary
+ ///
+ /// Author
+ /// Parent commentary
+ /// Content of commentary
+ /// Byte Array
+ /// New CommentID
+ Comment Respond(GDID gAuthorNode, CommentID parent, string content, byte[] data);
+
+ ///
+ /// Updates existing rating by ID
+ ///
+ GraphChangeStatus Update(CommentID ratingId, RatingValue value, string content, byte[] data);
+
+ ///
+ /// Delete comment
+ ///
+ /// Existing Comment ID
+ GraphChangeStatus DeleteComment(CommentID commentId);
+
+ ///
+ /// Updates likes/dislikes
+ ///
+ GraphChangeStatus Like(CommentID commentId, int deltaLike, int deltaDislike);
+
+ ///
+ /// Check if target node has existing comment, made by author
+ ///
+ /// Target node
+ /// Author
+ /// Scope of comments
+ bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension);
+
+ ///
+ /// Returns summary ratings per node, dimensions and create date (rating epochs)
+ ///
+ IEnumerable GetNodeSummaries(GDID gNode);
+
+ ///
+ /// Returns comments for Target Node
+ ///
+ IEnumerable Fetch(CommentQuery query);
+
+ ///
+ /// Returns comments for comment by Comment ID
+ ///
+ IEnumerable FetchResponses(CommentID commentId);
+
+ ///
+ /// Returns comment complaints by comment ID
+ ///
+ ///
+ ///
+ IEnumerable FetchComplaints(CommentID commentId);
+
+ ///
+ /// Return comment by comment ID
+ ///
+ Comment GetComment(CommentID commentId);
+
+ ///
+ /// Complain about comment
+ ///
+ GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message);
+
+ ///
+ /// Justify moderated comment
+ ///
+ GraphChangeStatus Justify(CommentID commentID);
+ }
+
+ public interface IGraphCommentSystemClient : IGraphCommentSystem, IAgniServiceClient
+ {
+ //todo Add async versions
+ }
+
+ public struct CommentQuery : IEquatable
+ {
+ //todo design
+ public const int COMMENT_BLOCK_SIZE = 32;
+
+ public static CommentQuery MakeNew(GDID gTargetNode, string dimension, CommentOrderType orderType, bool asc, DateTime asOfDate, int blockIndex)
+ {
+ return new CommentQuery(gTargetNode, dimension, orderType, asc, asOfDate, blockIndex);
+ }
+
+ internal CommentQuery(GDID gTargetNode, string dimension, CommentOrderType orderType, bool asc, DateTime asOfDate, int blockIndex)
+ {
+ G_TargetNode = gTargetNode;
+ Dimension = dimension;
+ OrderType = orderType;
+ Ascending = asc;
+ AsOfDate = asOfDate;
+ BlockIndex = blockIndex;
+ }
+
+ public readonly GDID G_TargetNode;
+ public readonly string Dimension;
+ public readonly CommentOrderType OrderType;
+ public readonly bool Ascending;
+
+ public readonly DateTime AsOfDate;
+ public readonly int BlockIndex;
+
+
+ public bool Equals(CommentQuery other)
+ {
+ return this.G_TargetNode == other.G_TargetNode &&
+ this.Dimension == other.Dimension &&
+ this.OrderType == other.OrderType &&
+ this.Ascending == other.Ascending &&
+ this.BlockIndex == other.BlockIndex
+ ;
+ }
+
+ public override int GetHashCode()
+ {
+ return G_TargetNode.GetHashCode() ^ BlockIndex;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (! (obj is CommentQuery)) return false;
+ return Equals((CommentQuery)obj);
+ }
+
+ }
+}
diff --git a/src/Agni.Social/Graph/IGraphEventSystem.cs b/src/Agni.Social/Graph/IGraphEventSystem.cs
new file mode 100644
index 0000000..ba3d640
--- /dev/null
+++ b/src/Agni.Social/Graph/IGraphEventSystem.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using NFX.Glue;
+using NFX.DataAccess.Distributed;
+
+using Agni.Contracts;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Handles social graph functionality dealing with event subscription and broadcasting
+ ///
+ [Glued]
+ [LifeCycle(ServerInstanceMode.Singleton)]
+ public interface IGraphEventSystem : IAgniService
+ {
+ ///
+ /// Emits the event - notifies all subscribers (watchers, friends etc.) about the event.
+ /// The physical notification happens via IGraphHost implementation
+ ///
+ void EmitEvent(Event evt);
+
+ ///
+ /// Subscribes recipient node to the emitter node. Unlike friends the susbscription connection is uni-directional
+ ///
+ void Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters);
+
+ ///
+ /// Removes the subscription. Unlike friends the subscription connection is uni-directional
+ ///
+ void Unsubscribe(GDID gRecipientNode, GDID gEmitterNode);
+
+ ///
+ /// Returns an estimated approximate number of subscribers that an emitter has
+ ///
+ long EstimateSubscriberCount(GDID gEmitterNode);
+
+ ///
+ /// Returns Subscribers for Emitter from start position
+ ///
+ IEnumerable GetSubscribers(GDID gEmitterNode, long start, int count);
+ }
+
+ ///
+ /// Contract for client of IGraphEventSystem svc
+ ///
+ public interface IGraphEventSystemClient : IAgniServiceClient, IGraphEventSystem
+ {
+ //todo Add async versions
+ }
+}
diff --git a/src/Agni.Social/Graph/IGraphFriendSystem.cs b/src/Agni.Social/Graph/IGraphFriendSystem.cs
new file mode 100644
index 0000000..f3dedef
--- /dev/null
+++ b/src/Agni.Social/Graph/IGraphFriendSystem.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+
+using NFX.Glue;
+using NFX.DataAccess.Distributed;
+
+using Agni.Contracts;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Handles the social graph functionality that deals with friend connection, friend list tagging and connection approval
+ ///
+ [Glued]
+ [LifeCycle(ServerInstanceMode.Singleton)]
+ public interface IGraphFriendSystem : IAgniService
+ {
+ ///
+ /// Returns an enumeration of friend list ids for the particular node
+ ///
+ IEnumerable GetFriendLists(GDID gNode);
+
+ ///
+ /// Adds a new friend list id for the particular node. The list id may not contain commas
+ ///
+ GraphChangeStatus AddFriendList(GDID gNode, string list, string description);
+
+ ///
+ /// Removes friend list id for the particular node
+ ///
+ GraphChangeStatus DeleteFriendList(GDID gNode, string list);
+
+ ///
+ /// Returns an enumeration of FriendConnection{GraphNode, approve date, direction, groups}
+ ///
+ IEnumerable GetFriendConnections(FriendQuery query);
+
+ ///
+ /// Adds a bidirectional friend connection between gNode and gFriendNode
+ /// If friend connection already exists updates the approve/ban stamp by the receiving party (otherwise approve is ignored)
+ /// If approve==null then no stamps are set, if true connection is approved given that gNode is not the one who initiated the connection,
+ /// false then connection is banned given that gNode is not the one who initiated the connection
+ ///
+ GraphChangeStatus AddFriend(GDID gNode, GDID gFriendNode, bool? approve);
+
+
+ ///
+ /// Assigns lists to the gNode (the operation is unidirectional - it only assigns the lists on the gNode).
+ /// Lists is a comma-separated list of friend list ids
+ ///
+ GraphChangeStatus AssignFriendLists(GDID gNode, GDID gFriendNode, string lists);
+
+
+ ///
+ /// Deletes friend connections. The operation drops both connections from node and friend
+ ///
+ GraphChangeStatus DeleteFriend(GDID gNode, GDID gFriendNode);
+ }
+
+
+ ///
+ /// Represents query parameters sent to IGraphFriendSystem.GetFriendConnections(query)
+ ///
+ public struct FriendQuery
+ {
+ public FriendQuery(GDID gNode, FriendStatusFilter status, string orgQry, string lists, int fetchStart, int fetchCount)
+ {
+ G_Node = gNode;
+ Status = status;
+ OriginQuery = orgQry;
+ Lists = lists;
+ FetchStart = fetchStart;
+ FetchCount = fetchCount;
+ }
+
+ /// Node for which friends are returned
+ public readonly GDID G_Node;
+
+ public readonly FriendStatusFilter Status;
+
+ /// Pass expression with * to search by name
+ public readonly string OriginQuery;
+
+ /// A comma-delimited list of friend list ids, null = all
+ public readonly string Lists;
+
+ /// From what position to start fetching
+ public readonly int FetchStart;
+
+ /// How many records to fetch
+ public readonly int FetchCount;
+ }
+
+ ///
+ /// Contract for client of IGraphFriendSystem svc
+ ///
+ public interface IGraphFriendSystemClient : IAgniServiceClient, IGraphFriendSystem
+ {
+ //todo Add async versions
+ }
+}
diff --git a/src/Agni.Social/Graph/IGraphNodeSystem.cs b/src/Agni.Social/Graph/IGraphNodeSystem.cs
new file mode 100644
index 0000000..8cfd81c
--- /dev/null
+++ b/src/Agni.Social/Graph/IGraphNodeSystem.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+
+using NFX.Glue;
+using NFX.DataAccess.Distributed;
+
+using Agni.Contracts;
+
+namespace Agni.Social.Graph
+{
+ ///
+ /// Handles the base social graph functionality such as CRUD of graph nodes (users, forums, groups etc..)
+ ///
+ [Glued]
+ public interface IGraphNodeSystem : IAgniService
+ {
+ ///
+ /// Saves the GraphNode instances into the system.
+ /// If a node with such ID already exists, updates it, otherwise creates a new node
+ /// Return GDID Node
+ ///
+ GraphChangeStatus SaveNode(GraphNode node);
+
+ ///
+ /// Fetches the GraphNode by its unique GDID or unassigned node if not found
+ ///
+ GraphNode GetNode(GDID gNode);
+
+ ///
+ /// Deletes node by GDID
+ ///
+ GraphChangeStatus DeleteNode(GDID gNode);
+
+ ///
+ /// Undeletes node by GDID
+ ///
+ GraphChangeStatus UndeleteNode(GDID gNode);
+
+ ///
+ /// Physically removes node by GDID from database
+ ///
+ GraphChangeStatus RemoveNode(GDID gNode);
+ }
+
+ ///
+ /// Contract for client of IGraphSystem svc
+ ///
+ public interface IGraphNodeSystemClient : IAgniServiceClient, IGraphNodeSystem
+ {
+ //todo Add async versions
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/BaseRows.cs b/src/Agni.Social/Graph/Server/Data/BaseRows.cs
new file mode 100644
index 0000000..9e124dc
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/BaseRows.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+
+namespace Agni.Social.Graph.Server.Data
+{
+ public abstract class BaseRow : AmorphousTypedRow
+ {
+ public BaseRow() : base()
+ {
+
+ }
+ }
+
+ ///
+ /// Base class for all GraphRows
+ ///
+ public abstract class BaseRowWithGDID : BaseRow
+ {
+ public static UniqueSequenceAttribute GetGDIDAttribute(Type tRow)
+ {
+ if (!typeof(BaseRow).IsAssignableFrom(tRow))
+ throw new GraphException("{0}: {1} is not BaseRow".Args("GetGDIDAttribute", tRow.FullName));
+
+ var attr = UniqueSequenceAttribute.GetForRowType(tRow);
+ if (attr == null ||
+ attr.Scope.IsNullOrWhiteSpace() ||
+ attr.Sequence.IsNullOrWhiteSpace())
+ throw new GraphException("Both scope and sequence must be defined in UniqueSequenceAttribute decorating {0}".Args(tRow.FullName));
+
+ return attr;
+ }
+
+ public BaseRowWithGDID() : base()
+ {
+
+ }
+
+ public BaseRowWithGDID(bool newGdid) : base()
+ {
+ if (newGdid)
+ {
+ GDID = GenerateNewGDID();
+ }
+ }
+
+ ///
+ /// Generates new gdid properly scoping it and naming it for this row (see GDIDSequenceName)
+ ///
+ public static GDID GenerateNewGDID(Type t)
+ {
+ var attr = GetGDIDAttribute(t);
+ return GraphOperationContext.Instance.DataStore.GDIDGenerator.GenerateOneGDID(attr.Scope, attr.Sequence);
+ }
+
+ ///
+ /// Generates new gdid properly scoping it and naming it for this row (see GDIDSequenceName)
+ ///
+ public GDID GenerateNewGDID()
+ {
+ return BaseRowWithGDID.GenerateNewGDID(this.GetType());
+ }
+
+ ///
+ /// Primary KEY
+ ///
+ [Field(targetName: TargetedAttribute.ANY_TARGET, required: true, key: true, visible: false)]
+ public GDID GDID { get ; set; }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/CommentRow.cs b/src/Agni.Social/Graph/Server/Data/CommentRow.cs
new file mode 100644
index 0000000..773a9dc
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/CommentRow.cs
@@ -0,0 +1,93 @@
+using System;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// Comment data stored in COMMENT area. Every comment has a unique ID, but is briefcased by G_COMMENTVOLUME
+ ///
+ [Table(name: "tbl_comment")]
+ [UniqueSequence(SocialConsts.MDB_AREA_COMMENT, "comment")]
+ public sealed class CommentRow : BaseRowWithGDID
+ {
+ public CommentRow() : base()
+ {
+ }
+
+ public CommentRow(bool newGdid) : base(newGdid)
+ {
+ }
+
+ ///
+ /// Briefcase key
+ ///
+ [Field(backendName: "G_VOL", required: true)]
+ public GDID G_CommentVolume { get; set; }
+
+ [Field(backendName: "DIM", required: true)]
+ public string Dimension { get; set; }
+
+ [Field(backendName: "G_ATR", required: true)]
+ public GDID G_AuthorNode { get; set; }
+
+ [Field(backendName: "G_TRG", required: true)]
+ public GDID G_TargetNode { get; set; }
+
+ [Field(backendName: "ROOT", required: true)]
+ public bool IsRoot { get; set; }
+
+ [Field(backendName: "G_PAR")]
+ public GDID? G_Parent { get; set; }
+
+ [Field(backendName: "MSG",
+ minLength: GSCommentMessage.MIN_LEN,
+ maxLength: GSCommentMessage.MAX_LEN,
+ required: true)]
+ public string Message { get; set; }
+
+ [Field(backendName: "DAT", required: true)]
+ public byte[] Data { get; set; }
+
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+
+ [Field(backendName: "LKE", required: true)]
+ public uint Like { get;set; }
+
+ [Field(backendName: "DIS", required: true)]
+ public uint Dislike { get; set; }
+
+ [Field(backendName: "CMP", required: true)]
+ public uint ComplaintCount { get; set; }
+
+ [Field(backendName: "PST", required: true)]
+ public string PublicationState { get; set; }
+
+ [Field(backendName: "RTG", required: true)]
+ public byte Rating { get; set; }
+
+ [Field(backendName: "RCNT", required: true)]
+ public uint ResponseCount { get; set; }
+
+ [Field(required: true)]
+ public bool In_Use { get ; set; }
+
+ ///
+ /// Update count of commentaries
+ ///
+ /// +/- Delta
+ public void UpdateResponseCount(int delta)
+ {
+ var isNegative = delta < 0;
+ if (isNegative && ResponseCount == 0)
+ return;
+
+ var abs = (uint)Math.Abs(delta);
+ ResponseCount = isNegative
+ ? ResponseCount - abs
+ : ResponseCount + abs;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/CommentVolumeRow.cs b/src/Agni.Social/Graph/Server/Data/CommentVolumeRow.cs
new file mode 100644
index 0000000..32cec65
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/CommentVolumeRow.cs
@@ -0,0 +1,32 @@
+using System;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ [Table(name: "tbl_commentvol")]
+ public sealed class CommentVolumeRow : BaseRow
+ {
+ /// Owner (target of comment such as PRODUCT NODE) GDID; Briefcase key for this row
+ [Field(backendName: "G_OWN", required: true, key: true)]
+ public GDID G_Owner { get; set; }
+
+ /// Link for Comment Volume; brifcase key for COMMENT; obtained from NODE sequence
+ [Field(backendName: "G_VOL", required: true, key: true)]
+ public GDID G_CommentVolume { get; set; }
+
+ ///
+ /// Rating dimensional
+ ///
+ [Field(backendName: "DIM", required: true)]
+ public string Dimension { get; set; }
+
+ /// Count message in volume
+ [Field(backendName: "CNT", required: true)]
+ public int Count { get; set; }
+
+ /// Create date volume
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/ComplaintRow.cs b/src/Agni.Social/Graph/Server/Data/ComplaintRow.cs
new file mode 100644
index 0000000..fb366c2
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/ComplaintRow.cs
@@ -0,0 +1,42 @@
+using System;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// Comment complaint data stored in COMMENT area
+ ///
+ [Table(name: "tbl_complaint")]
+ [UniqueSequence(SocialConsts.MDB_AREA_COMMENT, "complaint")]
+ public sealed class ComplaintRow : BaseRowWithGDID
+ {
+ public ComplaintRow() : base() {}
+ public ComplaintRow(bool newGdid) : base(newGdid) {}
+
+ [Field(backendName: "G_CMT", required: true)]
+ public GDID G_Comment { get; set; }
+
+ [Field(backendName: "G_ATH", required: true)]
+ public GDID G_AuthorNode { get; set; }
+
+ [Field(backendName: "KND",
+ required: true,
+ min: GSMessageType.MIN_LEN,
+ max: GSMessageType.MAX_LEN)]
+ public string Kind { get; set; }
+
+ [Field(backendName: "MSG",
+ minLength: GSCommentMessage.MIN_LEN,
+ maxLength: GSCommentMessage.MAX_LEN,
+ required: false)]
+ public string Message { get; set; }
+
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+
+ [Field(required: true)]
+ public bool In_Use { get ; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/FriendListRow.cs b/src/Agni.Social/Graph/Server/Data/FriendListRow.cs
new file mode 100644
index 0000000..1164964
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/FriendListRow.cs
@@ -0,0 +1,45 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// A list of named friend lists - a named group of friends like "family", "coworkers" etc.
+ ///
+ [Table(name: "tbl_friendlist")]
+ [UniqueSequence(SocialConsts.MDB_AREA_NODE, "flist")]
+ public sealed class FriendListRow : BaseRowWithGDID
+ {
+
+ public FriendListRow() : base()
+ {
+
+ }
+
+ public FriendListRow(bool gdid) : base(gdid)
+ {
+
+ }
+
+ /// Node tha this list belongs to - brief case key
+ [Field(backendName: "G_OWN", required: true)]
+ public GDID G_Owner { get; set; }
+
+ /// List ID
+ [Field(backendName: "LID", required: true, minLength: GSFriendListID.MIN_LEN, maxLength: GSFriendListID.MAX_LEN)]
+ public string List_ID { get; set; }
+
+ /// List description
+ [Field(backendName: "LDR", required: false, minLength: GSFriendListID.MIN_LEN, maxLength: GSFriendListID.MAX_LEN)]
+ public string List_Description { get; set; }
+
+ /// When created
+ [Field(backendName: "CDT", required: true)]
+ public DateTime? Create_Date { get; set; }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/FriendRow.cs b/src/Agni.Social/Graph/Server/Data/FriendRow.cs
new file mode 100644
index 0000000..b4144a4
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/FriendRow.cs
@@ -0,0 +1,62 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// GraphNode data
+ ///
+ [Table(name: "tbl_friend")]
+ [UniqueSequence(SocialConsts.MDB_AREA_NODE, "friend")]
+ public sealed class FriendRow : BaseRowWithGDID
+ {
+
+ public FriendRow() : base()
+ {
+
+ }
+
+ public FriendRow(bool newGdid) : base(newGdid)
+ {
+
+ }
+
+ /// Owner GDID; Briefcase key
+ [Field(backendName: "G_OWN", required: false)]
+ public GDID G_Owner { get; set; }
+
+ /// Pointer to a friend
+ [Field(backendName: "G_FND", required: true)]
+ public GDID G_Friend { get; set; }
+
+ /// When connection formed
+ [Field(backendName: "G_RDT", required: true)]
+ public DateTime Request_Date { get; set; }
+
+ /// If set, then approved
+ [Field(backendName: "G_SDT", required: true)]
+ public DateTime Status_Date { get; set; }
+
+ /// Approval status
+ [Field(backendName: "STS", required: true, valueList: GSFriendStatus.VALUE_LIST)]
+ public string Status { get; set; }
+
+ /// Who requested friendship
+ [Field(backendName: "DIR", required: true, valueList: GSFriendshipRequestDirection.VALUE_LIST)]
+ public string Direction { get; set; }
+
+ /// Who can see this connection - cascades from user profile
+ [Field(backendName: "VIS", required: true, valueList: GSFriendVisibility.VALUE_LIST)]
+ public string Visibility { get; set; }
+
+ /// Comma-separated list of friend list Ids
+ [Field(backendName: "LST", required: false)]
+ public string Lists { get; set; }
+
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/NodeRatingRow.cs b/src/Agni.Social/Graph/Server/Data/NodeRatingRow.cs
new file mode 100644
index 0000000..dbed2a0
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/NodeRatingRow.cs
@@ -0,0 +1,169 @@
+using System;
+
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// The table contains data for nodes that have rating. 1:M per dimension to Node extension
+ ///
+ [Table(name: "tbl_noderating")]
+ public sealed class NodeRatingRow : BaseRow
+ {
+ ///
+ /// Primary key GDID
+ ///
+ [Field(backendName: "G_NOD", required: true, key: true)]
+ public GDID G_Node { get; set; }
+
+ ///
+ /// Primary key, Dimension of rating, e.g. if we rate cars, the dims are 'reliability', 'driving" etc.
+ ///
+ [Field(backendName: "DIM", required: true, key: true)]
+ public string Dimension { get; set; }
+
+ ///
+ /// Also known as epoch
+ ///
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+
+ ///
+ /// When was the node's rating last changed
+ ///
+ [Field(backendName: "LCD", required: true)]
+ public DateTime Last_Change_Date { get; set; }
+
+ ///
+ /// Count of commetaries
+ ///
+ [Field(backendName: "CNT", required: true)]
+ public ulong Cnt { get; set; }
+
+ ///
+ /// Rating star 1 count
+ ///
+ [Field(backendName: "RTG1", required: true)]
+ public ulong Rating1 { get; set; }
+
+ ///
+ /// Rating star 2 count
+ ///
+ [Field(backendName: "RTG2", required: true)]
+ public ulong Rating2 { get; set; }
+
+ ///
+ /// Rating star 3 count
+ ///
+ [Field(backendName: "RTG3", required: true)]
+ public ulong Rating3 { get; set; }
+
+ ///
+ /// Rating star 4 count
+ ///
+ [Field(backendName: "RTG4", required: true)]
+ public ulong Rating4 { get; set; }
+
+ ///
+ /// Rating star 5 count
+ ///
+ [Field(backendName: "RTG5", required: true)]
+ public ulong Rating5 { get; set; }
+
+ ///
+ /// Total amount of ratings
+ ///
+ public ulong TotalRatings
+ {
+ get { return Rating1 + Rating2 + Rating3 + Rating4 + Rating5; }
+ }
+
+ ///
+ /// Average rating
+ ///
+ public float Rating
+ {
+ get
+ {
+ var tr = (float) TotalRatings;
+ return tr > 0f
+ ? ((Rating1*1) +
+ (Rating2*2) +
+ (Rating3*3) +
+ (Rating4*4) +
+ (Rating5*5)
+ )/tr
+ : 0f;
+ }
+ }
+
+ ///
+ /// Decrease or increase rating by delta
+ ///
+ /// Star 1-2-3-4-5
+ /// +/- Delta
+ public void UpdateRating(RatingValue value, int delta)
+ {
+ var isNegative = delta < 0;
+ var abs = (ulong) Math.Abs(delta);
+ switch (value)
+ {
+ // if isNegative == true and rating counter == 0, decreasing ulong will make field MAX ulong.
+ // so, need to check it
+ case RatingValue.Star1:
+ if (isNegative && Rating1 == 0)
+ break;
+ Rating1 = isNegative
+ ? Rating1 - abs
+ : Rating1 + abs;
+ break;
+ case RatingValue.Star2:
+ if (isNegative && Rating2 == 0)
+ break;
+ Rating2 = isNegative
+ ? Rating2 - abs
+ : Rating2 + abs;
+ break;
+ case RatingValue.Star3:
+ if (isNegative && Rating3 == 0)
+ break;
+ Rating3 = isNegative
+ ? Rating3 - abs
+ : Rating3 + abs;
+ break;
+ case RatingValue.Star4:
+ if (isNegative && Rating4 == 0)
+ break;
+ Rating4 = isNegative
+ ? Rating4 - abs
+ : Rating4 + abs;
+ break;
+ case RatingValue.Star5:
+ if (isNegative && Rating5 == 0)
+ break;
+ Rating5 = isNegative
+ ? Rating5 - abs
+ : Rating5 + abs;
+ break;
+ }
+ }
+
+ ///
+ /// Update count of commentaries
+ ///
+ /// +/- Delta
+ public void UpdateCount(int delta)
+ {
+ var isNegative = delta < 0;
+ if (isNegative && Cnt == 0)
+ return;
+
+ var abs = (ulong) Math.Abs(delta);
+ Cnt = isNegative
+ ? Cnt - abs
+ : Cnt + abs;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/NodeRow.cs b/src/Agni.Social/Graph/Server/Data/NodeRow.cs
new file mode 100644
index 0000000..356aa53
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/NodeRow.cs
@@ -0,0 +1,66 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ ///
+ /// GraphNode data
+ ///
+ [Table(name: "tbl_node")]
+ [UniqueSequence(SocialConsts.MDB_AREA_NODE, "node")]
+ public sealed class NodeRow : BaseRowWithGDID
+ {
+ public static GDID GenerateNewNodeRowGDID()
+ {
+ return GenerateNewGDID(typeof(NodeRow));
+ }
+
+ public NodeRow() : base()
+ {
+
+ }
+
+ public NodeRow(bool newGdid) : base(newGdid)
+ {
+ }
+
+ [Field(backendName: "TYP", required: true, minLength: GSNodeType.MIN_LEN, maxLength: GSNodeType.MAX_LEN)]
+ public string Node_Type { get; set; }
+
+ ///
+ /// Stores GDID of origin shard
+ ///
+ [Field(backendName: "G_OSH", required: true)]
+ public GDID G_OriginShard { get; set; }
+
+ ///
+ /// Stores the GDID of the origin entity
+ ///
+ [Field(backendName: "G_ORI", required: true)]
+ public GDID G_Origin { get; set; }
+
+ ///
+ /// Not required for subscriber link
+ ///
+ [Field(backendName: "ONM", required: true)]
+ public string Origin_Name { get; set; }
+
+ [Field(backendName: "ODT", required: false)]
+ public byte[] Origin_Data { get; set; }
+
+ [Field(backendName: "CDT", required: true)]
+ public DateTime? Create_Date { get; set; }
+
+ /// Default Friend Visibility
+ [Field(backendName: "FVI", required: false, valueList: GSFriendVisibility.VALUE_LIST)]
+ public string Friend_Visibility { get; set; }
+
+ [Field(required: true)]
+ public bool In_Use { get ; set; }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/Queries.cs b/src/Agni.Social/Graph/Server/Data/Queries.cs
new file mode 100644
index 0000000..a643223
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Queries.cs
@@ -0,0 +1,379 @@
+using System;
+using Agni.Social.Graph.Server.Data.Schema;
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX.Wave.Instrumentation;
+
+namespace Agni.Social.Graph.Server.Data
+{
+ internal static class Queries
+ {
+
+ #region Nodes and Subscribers
+
+ public static Query FindOneNodeByGDID(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.FindOneNodeByGDID")
+ {
+ new Query.Param("pgnode", gNode)
+ };
+ }
+
+ public static Query ChangeInUseNodeByGDID(GDID gNode, bool isDel) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.DeleteOneNodeByGDID")
+ {
+ new Query.Param("pgnode", gNode),
+ new Query.Param("pInUse", isDel ? "F" : "T")
+ };
+ }
+
+ public static Query CountSubscribers(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.CountSubscribers")
+ {
+ new Query.Param("pNode",gNode)
+ };
+ }
+
+ public static Query FindSubscriber(SubscriberVolumeRow volume, GDID gSubscriber) where TRow:Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.FindSubscriber")
+ {
+ new Query.Param("pVol",volume),
+ new Query.Param("pSub",gSubscriber)
+ };
+ }
+
+ public static Query FindSubscriberVolumes(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.FindSubscriberVolumes")
+ {
+ new Query.Param("pNode",gNode)
+ };
+ }
+
+ public static Query CountSubscriberVolumes(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.CountSubscriberVolumes")
+ {
+ new Query.Param("pNode",gNode)
+ };
+ }
+
+ public static Query FindSubscribers(GDID gVolume, long start, int count) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.FindSubscribers")
+ {
+ new Query.Param("pVol",gVolume),
+ new Query.Param("pStart",start),
+ new Query.Param("pCount",count)
+ };
+ }
+
+ public static Query GetNextVolume(GDID gNode, int start) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.GetNextVolume")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pStart", start)
+ };
+ }
+
+ public static Query RemoveNode(GDID gNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.RemoveNode")
+ {
+ new Query.Param("pNode", gNode)
+ };
+ }
+
+ public static Query RemoveSubVol(GDID gNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.RemoveSubVol")
+ {
+ new Query.Param("pNode", gNode)
+ };
+ }
+
+ public static Query RemoveSubscribers(GDID gVol)
+ {
+ return new Query("Graph.Server.Data.Scripts.Node.RemoveSubscribers")
+ {
+ new Query.Param("pVol", gVol)
+ };
+ }
+
+ #endregion
+
+ #region Friend
+
+ public static Query FindOneFriendByNodeAndFriend(GDID gNode, GDID gFriendNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.FindOneFriendByNodeAndFriend")
+ {
+ new Query.Param("pown", gNode),
+ new Query.Param("pfnd", gFriendNode)
+ };
+ }
+
+ public static Query FindFriends(FriendQuery query) where TRow : Row
+ {
+ var status = "%";
+ switch (query.Status)
+ {
+ case FriendStatusFilter.Approved:
+ status = GSFriendStatus.APPROVED;
+ break;
+ case FriendStatusFilter.Banned:
+ status = GSFriendStatus.BANNED;
+ break;
+ case FriendStatusFilter.PendingApproval:
+ status = GSFriendStatus.PENDING;
+ break;
+ }
+
+ return new Query("Graph.Server.Data.Scripts.Friend.FindFriends")
+ {
+ new Query.Param("pNode", query.G_Node),
+ new Query.Param("pList", query.Lists),
+ new Query.Param("pStatus", status),
+ new Query.Param("pFetchStart", query.FetchStart),
+ new Query.Param("pFetchCount", query.FetchCount)
+ };
+ }
+
+ public static Query CountFriends(GDID gNode) where TRow:Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.CountFriends")
+ {
+ new Query.Param("pG_Node",gNode)
+ };
+ }
+
+ public static Query FindFriendListByNode(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.FindFriendListByNode")
+ {
+ new Query.Param("pgnode", gNode)
+ };
+ }
+
+ public static Query FindAllFriends(GDID gNode) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.FindAllFriends")
+ {
+ new Query.Param("pNode", gNode)
+ };
+ }
+
+ public static Query DeleteFriendByNode(GDID gNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.DeleteFriendByNode")
+ {
+ new Query.Param("gG_Node", gNode)
+ };
+ }
+
+ public static Query DeleteFriendByNodeAndFriend(GDID gNode, GDID gFriendNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.DeleteFriendByNodeAndFriend")
+ {
+ new Query.Param("pown", gNode),
+ new Query.Param("pfnd", gFriendNode),
+ new Query.Param("pdt", App.TimeSource.UTCNow)
+ };
+ }
+
+ public static Query DeleteFriendListByListId(GDID gNode, string list)
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.DeleteFriendListByListId")
+ {
+ new Query.Param("pgnode", gNode),
+ new Query.Param("plistid", list)
+ };
+ }
+
+ public static Query RemoveFriendByNode(GDID gNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.RemoveFriendByNode")
+ {
+ new Query.Param("pNode", gNode)
+ };
+ }
+
+ public static Query RemoveFriendListByNode(GDID gNode)
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.RemoveFriendListByNode")
+ {
+ new Query.Param("pNode", gNode)
+ };
+ }
+
+ public static Query GetNextFriend(GDID gNode, int start) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Friend.GetNextFriend")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pStart", start)
+ };
+ }
+
+ #endregion
+
+ #region Comments and ratings
+
+ public static Query FindNodeRating(GDID gNode, string dimension) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindNodeRating")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDim", dimension)
+ };
+ }
+
+ public static Query FindNodeRatings(GDID gNode, DateTime dt) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindNodeRatings")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDT", dt)
+ };
+ }
+
+ public static Query ClearNodeRating(GDID gNode, string dimension, DateTime utc)
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.ClearNodeRating")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDim", dimension),
+ new Query.Param("pCDT", utc)
+ };
+ }
+
+ public static Query ClearNodeRatings(GDID gNode, DateTime utc)
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.ClearNodeRatings")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pCDT", utc)
+ };
+ }
+
+ public static Query UpdateLike(GDID gVolume, GDID gComment, int deltaLike, int deltaDislike)
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.UpdateLike")
+ {
+ new Query.Param("pVolume", gVolume),
+ new Query.Param("pComment", gComment),
+ new Query.Param("pdtLike", deltaLike),
+ new Query.Param("pdtDislike", deltaDislike)
+ };
+ }
+
+ public static Query CancelComplaintsByComment(GDID gComment)
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.CancelComplaintsByComment")
+ {
+ new Query.Param("pG_Comment", gComment)
+ };
+ }
+
+ public static Query FindCommentVolume(GDID gNode, GDID gVolume) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindCommentVolume")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pVol", gVolume)
+ };
+ }
+
+ public static Query FindEmptyCommentVolume(GDID gNode, string dimension, int maxCount) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindEmptyCommentVolume")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDim", dimension),
+ new Query.Param("pMaxCount", maxCount)
+ };
+ }
+
+ public static Query FindCommentByGDID(GDID gComment) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindCommentByGDID")
+ {
+ new Query.Param("pGDID", gComment)
+ };
+ }
+
+ public static Query FindComments(GDID gNode, string dimension, bool isRoot) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindComments")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDim", dimension),
+ new Query.Param("pRoot", isRoot)
+ };
+ }
+
+ public static Query HasCommentsCreatedByAuthor(GDID gNode, GDID gAuthor, string dimension) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.HasCommentsCreatedByAuthor")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pAuthor", gAuthor),
+ new Query.Param("pDim", dimension)
+ };
+ }
+
+ public static Query DeleteResponses(CommentID commentId)
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.DeleteResponses")
+ {
+ new Query.Param("pParent", commentId.G_Comment),
+ new Query.Param("pVolume", commentId.G_Volume)
+ };
+ }
+
+ public static Query CountResponses(CommentID commentId) where TRow: Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.CountResponses")
+ {
+ new Query.Param("pComment", commentId.G_Comment),
+ new Query.Param("pVolume", commentId.G_Volume)
+ };
+ }
+
+ public static Query FindResponses(CommentID commentId) where TRow:Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindResponses")
+ {
+ new Query.Param("pComment", commentId.G_Comment),
+ new Query.Param("pVolume", commentId.G_Volume)
+ };
+ }
+
+ public static Query FindComplaints(GDID gComment) where TRow : Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindComplaints")
+ {
+ new Query.Param("pComment", gComment)
+ };
+ }
+
+ public static Query FindCommentVolumes(GDID gNode, string dimension,int count, DateTime cdt) where TRow: Row
+ {
+ return new Query("Graph.Server.Data.Scripts.Rating.FindCommentVolumes")
+ {
+ new Query.Param("pNode", gNode),
+ new Query.Param("pDIM", dimension),
+ new Query.Param("pCDT", cdt),
+ new Query.Param("cnt", count)
+ };
+ }
+
+ #endregion
+
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/Domains.cs b/src/Agni.Social/Graph/Server/Data/Schema/Domains.cs
new file mode 100644
index 0000000..935553f
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/Domains.cs
@@ -0,0 +1,394 @@
+using System;
+using System.CodeDom;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using NFX;
+using NFX.RelationalModel;
+using NFX.RelationalModel.DataTypes;
+
+namespace Agni.Social.Graph.Server.Data.Schema
+{
+ public abstract class GSDomain : RDBMSDomain
+ {
+ protected GSDomain() : base() { }
+ }
+
+
+ public abstract class GSEnum : GSDomain
+ {
+ public DBCharType Type;
+
+ public int Size;
+
+ public string[] Values;
+
+ protected GSEnum(DBCharType type, string values)
+ {
+ Type = type;
+ var vlist = values.Split('|');
+ Size = vlist.Max(v => v.Trim().Length);
+ if (Size < 1) Size = 1;
+ Values = vlist;
+ }
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return Type == DBCharType.Varchar ? "VARCHAR({0})".Args(Size) : "CHAR({0})".Args(Size);
+ }
+
+ public override string GetColumnCheckScript(RDBMSCompiler compiler, RDBMSEntity column, Compiler.Outputs outputs)
+ {
+ var enumLine = string.Join(", ", Values.Select(v => compiler.EscapeString(v.Trim())));
+ return compiler.TransformKeywordCase("check ({0} in ({1}))")
+ .Args(
+ compiler.GetQuotedIdentifierName(RDBMSEntityType.Column, column.TransformedName),
+ enumLine
+ );
+ }
+ }
+
+
+ public abstract class GSGDID : GSDomain
+ {
+ public readonly bool Required;
+
+ public GSGDID(bool required)
+ {
+ Required = required;
+ }
+
+ public override bool? GetColumnRequirement(RDBMSCompiler compiler)
+ {
+ return Required;
+ }
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return Required ? "BINARY(12)" : "VARBINARY(12)";
+ }
+ }
+
+
+ public abstract class GSGDIDRef : GSGDID
+ {
+ public GSGDIDRef(bool required) : base(required) { }
+
+ public override void TransformColumnName(RDBMSCompiler compiler, RDBMSEntity column)
+ {
+ column.TransformedName = "G_{0}".Args(column.TransformedName);
+ }
+ }
+
+
+ public sealed class GSRequiredGDID : GSGDID { public GSRequiredGDID() : base(true) { } }
+ public sealed class GSNullableGDID : GSGDID { public GSNullableGDID() : base(false) { } }
+ public sealed class GSRequiredGDIDRef : GSGDIDRef { public GSRequiredGDIDRef() : base(true) { } }
+ public sealed class GSNullableGDIDRef : GSGDIDRef { public GSNullableGDIDRef() : base(false) { } }
+
+
+ public sealed class GSBool : GSEnum { public const int MAX_LEN = 1; public GSBool() : base(DBCharType.Char, "T|F") { } }
+
+ public sealed class GSDescription : GSDomain
+ {
+ public const int MAX_LEN = 64;
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+
+ public sealed class GSNodeName : GSDomain
+ {
+ public const int MAX_LEN = 48;
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+
+ ///
+ /// Node type such as: User, Room, Forum, Group etc.
+ ///
+ public sealed class GSNodeType : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = 8; // So we can pack in ulong
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "char({0})".Args(MAX_LEN);
+ }
+ }
+
+ /// Such as Subscription parameters
+ public sealed class GSParameters : GSDomain
+ {
+ public const int MIN_LEN = 3;
+ public const int MAX_LEN = 1024;
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+
+ ///
+ /// No second fraction resolution
+ ///
+ public sealed class GSTimestamp : GSDomain
+ {
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "datetime(0)";
+ }
+ }
+
+ public sealed class GSFriendListID : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = 16;
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+ public sealed class GSFriendListIDs : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = (GSFriendListID.MAX_LEN+1/*for comma*/)*12;//12 lists max
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+ public sealed class GSFriendStatus : GSEnum
+ {
+ public const int MAX_LEN = 1;
+
+ public const string PENDING = "P";
+ public const string APPROVED = "A";
+ public const string DENIED = "D";
+ public const string BANNED = "B";
+
+ public const string VALUE_LIST = "P: Pending, A: Approved, D: Denied, B: Banned";
+
+ public static string ToDomainString(FriendStatus status)
+ {
+ switch (status)
+ {
+ case FriendStatus.Approved: return GSFriendStatus.APPROVED;
+ case FriendStatus.Denied: return GSFriendStatus.DENIED;
+ case FriendStatus.Banned: return GSFriendStatus.BANNED;
+ default: return GSFriendStatus.PENDING;
+ }
+ }
+
+ public static FriendStatus ToFriendStatus(string status)
+ {
+ if (status.EqualsOrdIgnoreCase(GSFriendStatus.APPROVED))return FriendStatus.Approved;
+ if (status.EqualsOrdIgnoreCase(GSFriendStatus.DENIED)) return FriendStatus.Denied;
+ if (status.EqualsOrdIgnoreCase(GSFriendStatus.BANNED)) return FriendStatus.Banned;
+
+ return FriendStatus.Pending;
+ }
+
+ public GSFriendStatus() : base(DBCharType.Char, "P|A|D|B") { }
+ }
+
+
+ public sealed class GSFriendshipRequestDirection : GSEnum
+ {
+ public const int MAX_LEN = 1;
+
+ public const string I = "I";
+ public const string FRIEND = "F";
+
+ public const string VALUE_LIST = "I: I myself, F: Friend";
+
+ public static string ToDomainString(FriendshipRequestDirection dir)
+ {
+ if (dir==FriendshipRequestDirection.Friend) return FRIEND;
+ return I;
+ }
+
+ public static FriendshipRequestDirection ToFriendshipRequestDirection(string status)
+ {
+ if (status.EqualsOrdIgnoreCase(GSFriendshipRequestDirection.FRIEND))return FriendshipRequestDirection.Friend;
+
+ return FriendshipRequestDirection.I;
+ }
+
+ public GSFriendshipRequestDirection() : base(DBCharType.Char, "I|F") { }
+ }
+
+
+ public sealed class GSFriendVisibility : GSEnum
+ {
+ public const int MAX_LEN = 1;
+
+ public const string ANYONE = "A";
+ public const string PUBLIC = "P";
+ public const string FRIENDS = "F";
+ public const string PRIVATE = "T";
+
+ public const string VALUE_LIST = "A: Anyone, P: Public, F: Friends, T: Private";
+
+ public static string ToDomainString(FriendVisibility vis)
+ {
+ switch(vis)
+ {
+ case FriendVisibility.Anyone: return GSFriendVisibility.ANYONE;
+ case FriendVisibility.Friends: return GSFriendVisibility.FRIENDS;
+ case FriendVisibility.Public: return GSFriendVisibility.PUBLIC;
+ default: return GSFriendVisibility.PRIVATE;
+ }
+ }
+
+ public static FriendVisibility ToFriendVisibility(string status)
+ {
+ if (status.EqualsOrdIgnoreCase(GSFriendVisibility.ANYONE)) return FriendVisibility.Anyone;
+ if (status.EqualsOrdIgnoreCase(GSFriendVisibility.PUBLIC)) return FriendVisibility.Public;
+ if (status.EqualsOrdIgnoreCase(GSFriendVisibility.FRIENDS))return FriendVisibility.Friends;
+
+ return FriendVisibility.Private;
+ }
+
+ public GSFriendVisibility() : base(DBCharType.Char, "A|P|F|T") { }
+ }
+
+
+ public sealed class GSBSONData : GSDomain
+ {
+ public const int MAX_LEN = 256;
+
+ public readonly bool Required;
+
+ public GSBSONData()
+ {
+ Required = false;
+ }
+
+ public GSBSONData(bool required)
+ {
+ Required = required;
+ }
+
+ public override bool? GetColumnRequirement(RDBMSCompiler compiler)
+ {
+ return Required;
+ }
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return Required ? "BINARY({0})".Args(MAX_LEN) : "VARBINARY({0})".Args(MAX_LEN);
+ }
+ }
+
+ public class GSCounter : GSDomain
+ {
+ public GSCounter() : base() { }
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "BIGINT(8) UNSIGNED";
+ }
+ }
+
+ public sealed class GSMessageType : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = 8; // So we can pack in ulong
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "char({0})".Args(MAX_LEN);
+ }
+ }
+
+ public sealed class GSRating : GSDomain
+ {
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "tinyint";
+ }
+ }
+
+ public sealed class GSCommentMessage : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = 1024;
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "varchar({0})".Args(MAX_LEN);
+ }
+ }
+
+ public sealed class GSLike : GSDomain
+ {
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "int";
+ }
+ }
+
+ public sealed class GSPublicationState : GSEnum
+ {
+ public const int MAX_LEN = 1;
+
+ public const string PRIVATE = "R";
+ public const string PUBLIC = "P";
+ public const string FRIEND = "F";
+ public const string DELETED = "D";
+
+ public const string VALUE_LIST = "R: Private, P: Public, F:Friend, D:Deleted";
+
+ public static string ToDomainString(PublicationState status)
+ {
+ switch (status)
+ {
+ case PublicationState.Private: return GSPublicationState.PRIVATE;
+ case PublicationState.Public: return GSPublicationState.PUBLIC;
+ case PublicationState.Friend: return GSPublicationState.FRIEND;
+ case PublicationState.Deleted: return GSPublicationState.DELETED;
+ default: return GSPublicationState.PUBLIC;
+ }
+ }
+
+ public static PublicationState ToPublicationState(string status)
+ {
+ if (status.EqualsOrdIgnoreCase(GSPublicationState.PRIVATE))return PublicationState.Private;
+ if (status.EqualsOrdIgnoreCase(GSPublicationState.PUBLIC)) return PublicationState.Public;
+ if (status.EqualsOrdIgnoreCase(GSPublicationState.FRIEND)) return PublicationState.Friend;
+ if (status.EqualsOrdIgnoreCase(GSPublicationState.DELETED)) return PublicationState.Deleted;
+
+ return PublicationState.Public;
+ }
+
+ public GSPublicationState() : base(DBCharType.Char, "C|P") { }
+ }
+
+ public sealed class GSDimension : GSDomain
+ {
+ public const int MIN_LEN = 1;
+ public const int MAX_LEN = 8; // So we can pack in ulong
+
+ public override string GetTypeName(RDBMSCompiler compiler)
+ {
+ return "char({0})".Args(MAX_LEN);
+ }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/Graph.Comment.tables.MySQL.sql b/src/Agni.Social/Graph/Server/Data/Schema/Graph.Comment.tables.MySQL.sql
new file mode 100644
index 0000000..38f49d4
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/Graph.Comment.tables.MySQL.sql
@@ -0,0 +1,43 @@
+delimiter ;.
+-- Table tbl_comment
+create table `tbl_comment`
+(
+ `GDID` BINARY(12) not null,
+ `G_VOL` BINARY(12) not null comment 'Briefcase rating message',
+ `G_ATR` BINARY(12) not null comment 'Author',
+ `G_TRG` BINARY(12) not null comment 'What rated',
+ `DIM` char(8) not null comment 'Dimension',
+ `ROOT` CHAR(1) not null comment 'Is root',
+ `G_PAR` VARBINARY(12) comment 'This is a response to parent',
+ `MSG` varchar(1024) comment 'Comment message',
+ `DAT` VARBINARY(256) comment 'Comment message data. BSON',
+ `CDT` datetime(0) not null comment 'The UTC date of volume creation',
+ `LKE` int not null comment 'Liked',
+ `DIS` int not null comment 'Disliked',
+ `CMP` int not null comment 'Count of complaints',
+ `PST` CHAR(1) not null comment 'Publication state',
+ `RTG` tinyint not null comment 'Rating 1-5',
+ `RCNT` BIGINT(8) UNSIGNED not null comment 'Response count',
+ `IN_USE` CHAR(1) not null comment 'Logical Deletion flag',
+ constraint `pk_tbl_comment_primary` primary key (`GDID`)
+)
+ comment = 'Hold comments and rating'
+;.
+
+delimiter ;.
+-- Table tbl_complaint
+create table `tbl_complaint`
+(
+ `GDID` BINARY(12) not null,
+ `G_CMT` BINARY(12) not null comment 'Reference to comment that complaint is for',
+ `G_ATH` BINARY(12) not null comment 'Author graph node',
+ `KND` char(8) not null comment 'Kind of complaint',
+ `MSG` varchar(1024) comment 'Complaint message',
+ `CDT` datetime(0) not null comment 'The UTC date of complaint creation',
+ `IN_USE` CHAR(1) not null comment 'Logical Deletion flag',
+ constraint `pk_tbl_complaint_primary` primary key (`GDID`)
+)
+;.
+
+delimiter ;.
+ create index `idx_tbl_complaint_cmt` on `tbl_complaint`(`G_CMT`);.
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/Graph.Node.tables.MySQL.sql b/src/Agni.Social/Graph/Server/Data/Schema/Graph.Node.tables.MySQL.sql
new file mode 100644
index 0000000..7a0df80
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/Graph.Node.tables.MySQL.sql
@@ -0,0 +1,119 @@
+delimiter ;.
+-- Table tbl_node
+create table `tbl_node`
+(
+ `GDID` BINARY(12) not null,
+ `TYP` char(8) not null comment 'Type of node such as: User, Forum, Club etc.',
+ `G_OSH` BINARY(12) not null comment 'Origin sharding GDID',
+ `G_ORI` BINARY(12) not null comment 'Origin GDID',
+ `ONM` varchar(48) not null comment 'Origin string name',
+ `ODT` VARBINARY(256) comment 'Origin data. BSON',
+ `CDT` datetime(0) not null comment 'The UTC date of node creation',
+ `FVI` CHAR(1) not null comment 'Default friend visibility',
+ `IN_USE` CHAR(1) not null comment 'Logical Deletion flag',
+ constraint `pk_tbl_node_primary` primary key (`GDID`)
+)
+ comment = 'Holds Data about Graph Node which represent various socially-addressable entities in the host system, e.g. (PROD, 123, 45, \'SONY PLAYER\');(USR, 89, 23, \'Oleg Popov\')'
+;.
+
+delimiter ;.
+ create index `idx_tbl_node_ori` on `tbl_node`(`G_ORI`) comment 'Used to locate records by origin, the type and origin shard are purposely omitted from this index';.
+delimiter ;.
+-- Table tbl_friendlist
+create table `tbl_friendlist`
+(
+ `GDID` BINARY(12) not null,
+ `G_OWN` BINARY(12) not null comment 'Graph node that has named list',
+ `LID` varchar(16) not null comment 'Friend list ID, such as \'Work\', \'Family\'',
+ `LDR` varchar(64) comment 'List description',
+ `CDT` datetime(0) not null comment 'When was created',
+ constraint `pk_tbl_friendlist_primary` primary key (`GDID`)
+)
+ comment = 'Friend lists per node - a list is a named set of graph node connections, such as \'Family\', \'Coworkers\' etc.'
+;.
+
+delimiter ;.
+ create index `idx_tbl_friendlist_own` on `tbl_friendlist`(`G_OWN`);.
+delimiter ;.
+-- Table tbl_subscribervol
+create table `tbl_subscribervol`
+(
+ `G_OWN` BINARY(12) not null comment 'Owner/emitter, briefcase key',
+ `G_VOL` BINARY(12) not null comment 'Briefcase for subscribers; generated from NODE id',
+ `CNT` BIGINT(8) UNSIGNED not null comment 'Approximate count of subscribers in briefcase',
+ `CDT` datetime(0) not null comment 'The UTC date of volume creation',
+ constraint `pk_tbl_subscribervol_primary` primary key (`G_OWN`, `G_VOL`)
+)
+ comment = 'Subscription volume - splits large number of subscribers into a tree of volumes each sharded separately'
+;.
+
+delimiter ;.
+-- Table tbl_subscriber
+create table `tbl_subscriber`
+(
+ `G_VOL` BINARY(12) not null comment 'Who emits/to whom subscribed - briefcase key',
+ `G_SUB` BINARY(12) not null comment 'Who subscribes',
+ `STP` char(8) not null comment 'Type of node such as: User, Forum, Club etc.; denormalized from G_Subscriber for filtering',
+ `CDT` datetime(0) not null comment 'The UTC date of node subscription creation',
+ `PAR` VARBINARY(256) comment 'Subscription parameters - such as level of detail',
+ constraint `pk_tbl_subscriber_primary` primary key (`G_VOL`, `G_SUB`)
+)
+ comment = 'Holds node subscribers, sharded on G_VOL'
+;.
+
+delimiter ;.
+-- Table tbl_commentvol
+create table `tbl_commentvol`
+(
+ `G_OWN` BINARY(12) not null comment 'Owner/target of comment, such as: product, service',
+ `G_VOL` BINARY(12) not null comment 'Briefcase of comment area',
+ `DIM` char(8) not null comment 'Dimension - such as \'review\', \'qna\'; a volume BELONGS to the particular dimension',
+ `CNT` BIGINT(8) UNSIGNED not null comment 'Approximate count of messages in briefcase',
+ `CDT` datetime(0) not null comment 'The UTC date of volume creation',
+ constraint `pk_tbl_commentvol_primary` primary key (`G_OWN`, `G_VOL`)
+)
+ comment = 'Comment Volume - splits large number of comments into a tree of volumes each sharded in graph node area, kept separately in Comment Area'
+;.
+
+delimiter ;.
+-- Table tbl_friend
+create table `tbl_friend`
+(
+ `GDID` BINARY(12) not null,
+ `G_OWN` BINARY(12) not null comment 'A friend of WHO',
+ `G_FND` BINARY(12) not null comment 'A friend',
+ `RDT` datetime(0) not null comment 'The UTC date friend request',
+ `SDT` datetime(0) not null comment 'The UTC date of status',
+ `STS` CHAR(1) not null comment '[P]ending|[A]pproved|[D]enied|[B]anned',
+ `DIR` CHAR(1) not null comment '[I]am|[F]riend',
+ `VIS` CHAR(1) not null comment '[A]nyone|[P]ublic|[F]riend|[T]Private',
+ `LST` varchar(204) comment 'Friend lists comma-separated',
+ constraint `pk_tbl_friend_primary` primary key (`GDID`),
+ constraint `fk_tbl_friend_own` foreign key (`G_OWN`) references `tbl_node`(`GDID`)
+)
+ comment = 'Holds node\'s friends. The list is capped by the system at 9999 including pending request and approved friends. 16000 including banned friends'
+;.
+
+delimiter ;.
+ create unique index `idx_tbl_friend_uk` on `tbl_friend`(`G_OWN`, `G_FND`);.
+delimiter ;.
+ create index `idx_tbl_friend_friend` on `tbl_friend`(`G_FND`, `G_OWN`);.
+delimiter ;.
+-- Table tbl_noderating
+create table `tbl_noderating`
+(
+ `G_NOD` BINARY(12) not null comment 'A Node',
+ `DIM` char(8) not null comment 'Dimension',
+ `CNT` BIGINT(8) UNSIGNED not null default '0' comment 'Count of comments (even with rating 0)',
+ `RTG1` BIGINT(8) UNSIGNED not null default '0' comment 'Count rating for value 1',
+ `RTG2` BIGINT(8) UNSIGNED not null default '0' comment 'Count rating for value 2',
+ `RTG3` BIGINT(8) UNSIGNED not null default '0' comment 'Count rating for value 3',
+ `RTG4` BIGINT(8) UNSIGNED not null default '0' comment 'Count rating for value 4',
+ `RTG5` BIGINT(8) UNSIGNED not null default '0' comment 'Count rating for value 5',
+ `CDT` datetime(0) not null comment 'The UTC date of node rating creation',
+ `LCD` datetime(0) not null comment 'The UTC date of change rating',
+ constraint `pk_tbl_noderating_primary` primary key (`G_NOD`, `DIM`)
+)
+ comment = 'Rating node'
+;.
+
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/graph-comment.rschema b/src/Agni.Social/Graph/Server/Data/Schema/graph-comment.rschema
new file mode 100644
index 0000000..0641b6b
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/graph-comment.rschema
@@ -0,0 +1,43 @@
+schema
+{
+ include="graph-common.rschema"{}
+
+ table=Comment
+ {
+ comment="Hold comments and rating"
+
+ _call=/scripts/gdid{}
+
+ column=VOL { type=$(/$TRequiredGDIDRef) comment="Briefcase rating message" }
+ column=ATR { type=$(/$TRequiredGDIDRef) comment="Author" }
+ column=TRG { type=$(/$TRequiredGDIDRef) comment="What rated" }
+
+ column=DIM { type=$(/$TDimension) required=true comment="Dimension"}
+ column=ROOT { type=$(/$TBool) required=true comment="Is root"}
+ column=PAR { type=$(/$TNullableGDIDRef) comment="This is a response to parent" }
+ column=MSG { type=$(/$TCommentMessage) required=false comment="Comment message"}
+ column=DAT { type=$(/$TBSONData) required=false comment="Comment message data. BSON"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of volume creation"}
+ column=LKE { type=$(/$TLike) required=true comment="Liked"}
+ column=DIS { type=$(/$TLike) required=true comment="Disliked"}
+ column=CMP { type=$(/$TLike) required=true comment="Count of complaints"}
+ column=PST { type=$(/$TPublicationState) required=true comment="Publication state"}
+ column=RTG { type=$(/$TRating) required=true comment="Rating 1-5"}
+ column=RCNT { type=$(/$TCounter) required=true comment="Response count"}
+
+ _call=/scripts/in-use{}
+ }
+
+ table=complaint
+ {
+ _call=/scripts/gdid {}
+ column=CMT { type=$(/$TRequiredGDIDRef) comment="Reference to comment that complaint is for" }
+ column=ATH { type=$(/$TRequiredGDIDRef) comment="Author graph node" }
+ column=KND { type=$(/$TMessageType) required=true comment="Kind of complaint" }
+ column=MSG { type=$(/$TCommentMessage) required=false comment="Complaint message" }
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of complaint creation" }
+ _call=/scripts/in-use{}
+
+ index=cmt {column=CMT{}}
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/graph-common.rschema b/src/Agni.Social/Graph/Server/Data/Schema/graph-common.rschema
new file mode 100644
index 0000000..8abe027
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/graph-common.rschema
@@ -0,0 +1,49 @@
+schema
+{
+ PK_COLUMN = "GDID"
+
+ TRUE='T'
+ FALSE='F'
+
+ //Domain types used by Graph System
+ TRequiredGDID = "GSRequiredGDID"
+ TRequiredGDIDRef = "GSRequiredGDIDRef"
+ TNullableGDID = "GSNullableGDID"
+ TNullableGDIDRef = "GSNullableGDIDRef"
+
+ TBool = "GSBool"
+ TCounter = "GSCounter"
+
+ TRUE = 'T'
+ FALSE = 'F'
+
+ TTimestamp = "GSTimestamp"
+ TDescription = "GSDescription"
+ TBSONData = "GSBSONData"
+ TParameters = "GSParameters"
+
+ TNodeType = "GSNodeType"
+ TNodeName = "GSNodeName"
+
+ TFriendListID = "GSFriendListID"
+ TFriendListIDs = "GSFriendListIDs"
+ TFriendStatus = "GSFriendStatus"
+ TFriendshipReqDirection = "GSFriendshipRequestDirection"
+ TFriendVisibility = "GSFriendVisibility"
+
+ TMessageType = "GSMessageType"
+
+ TRating = "GSRating"
+ TCommentMessage = "GSCommentMessage"
+ TLike = "GSLike"
+
+ TPublicationState = "GSPublicationState"
+ TDimension = "GSDimension"
+
+ scripts
+ {
+ script-only=true
+ gdid { column=$(/$PK_COLUMN) { type=$(/$TRequiredGDID)} primary-key{ column=$(/$PK_COLUMN){} } }
+ in-use { column=In_Use { type=$(/$TBool) required=true comment="Logical Deletion flag"} }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Schema/graph-node.rschema b/src/Agni.Social/Graph/Server/Data/Schema/graph-node.rschema
new file mode 100644
index 0000000..c27edcb
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Schema/graph-node.rschema
@@ -0,0 +1,140 @@
+schema
+{
+ include="graph-common.rschema"{}
+
+ /*
+ NOTE
+ COLUMN NAMES are very short - this is done ON PURPOSE because
+ various databse protocol implementations may transmit column names in statements/fetches
+ while the Graph System is designed to handle hundredes of millions of rows per every server
+ with very many fetches/statements per second - the network traffic has to be minimized
+ */
+
+ table=Node
+ {
+ comment="Holds Data about Graph Node which represent various socially-addressable entities in the host system, e.g. (PROD, 123, 45, 'SONY PLAYER');(USR, 89, 23, 'Oleg Popov')"
+
+ _call=/scripts/gdid{}
+
+ column=TYP { type=$(/$TNodeType) required=true comment="Type of node such as: User, Forum, Club etc."}
+ column=OSH { type=$(/$TRequiredGDIDRef) comment="Origin sharding GDID"}
+ column=ORI { type=$(/$TRequiredGDIDRef) comment="Origin GDID"}
+ column=ONM { type=$(/$TNodeName) required=true comment="Origin string name"}
+ column=ODT { type=$(/$TBSONData) required=false comment="Origin data. BSON"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of node creation"}
+ column=FVI { type=$(/$TFriendVisibility) required=true comment="Default friend visibility"}
+
+ _call=/scripts/in-use{}
+
+ index=ori
+ {
+ comment="Used to locate records by origin, the type and origin shard are purposely omitted from this index"
+ column=ORI{}
+ }
+ }
+
+
+ table=FriendList
+ {
+ comment="Friend lists per node - a list is a named set of graph node connections, such as 'Family', 'Coworkers' etc."
+
+ _call=/scripts/gdid{}
+
+ column=OWN { type=$(/$TRequiredGDIDRef) comment="Graph node that has named list" }
+ column=LID { type=$(/$TFriendListID) required=true comment="Friend list ID, such as 'Work', 'Family'"}
+ column=LDR { type=$(/$TDescription) required=false comment="List description"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="When was created"}
+
+ index=own
+ {
+ column=OWN{}
+ }
+ }
+
+
+ table=SubscriberVol
+ {
+ comment="Subscription volume - splits large number of subscribers into a tree of volumes each sharded separately"
+
+ column=OWN { type=$(/$TRequiredGDIDRef) comment="Owner/emitter, briefcase key" }
+ column=VOL { type=$(/$TRequiredGDIDRef) comment="Briefcase for subscribers; generated from NODE id" }
+ column=CNT { type=$(/$TCounter) required=true comment="Approximate count of subscribers in briefcase"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of volume creation"}
+
+ primary-key {column=OWN{} column=VOL{}}
+ }
+
+ table=Subscriber
+ {
+ comment="Holds node subscribers, sharded on G_VOL"
+
+ column=VOL { type=$(/$TRequiredGDIDRef) comment="Who emits/to whom subscribed - briefcase key" }
+ column=SUB { type=$(/$TRequiredGDIDRef) comment="Who subscribes"}
+ column=STP { type=$(/$TNodeType) required=true comment="Type of node such as: User, Forum, Club etc.; denormalized from G_Subscriber for filtering"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of node subscription creation"}
+ column=PAR { type=$(/$TBSONData) comment="Subscription parameters - such as level of detail"}
+
+ primary-key {column=VOL{} column=SUB{}}
+ }
+
+ table=CommentVol
+ {
+ comment="Comment Volume - splits large number of comments into a tree of volumes each sharded in graph node area, kept separately in Comment Area"
+
+ column=OWN { type=$(/$TRequiredGDIDRef) comment="Owner/target of comment, such as: product, service" }
+ column=VOL { type=$(/$TRequiredGDIDRef) comment="Briefcase of comment area" }
+ column=DIM { type=$(/$TDimension) required=true comment="Dimension - such as 'review', 'qna'; a volume BELONGS to the particular dimension"}
+ column=CNT { type=$(/$TCounter) required=true comment="Approximate count of messages in briefcase"}
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of volume creation"}
+
+ primary-key { column=OWN{} column=VOL{} }
+ }
+
+ table=Friend
+ {
+ comment= "Holds node's friends. The list is capped by the system at 9999 including pending request and approved friends. 16000 including banned friends"
+
+ _call=/scripts/gdid{}
+
+ column=OWN { type=$(/$TRequiredGDIDRef) comment="A friend of WHO" reference{ table="node" column=$(/$PK_COLUMN) }}//this can be referenced because friends are always briefcased in the same shard
+ column=FND { type=$(/$TRequiredGDIDRef) comment="A friend"}
+ column=RDT { type=$(/$TTimestamp) required=true comment="The UTC date friend request"}
+ column=SDT { type=$(/$TTimestamp) required=true comment="The UTC date of status"}
+ column=STS { type=$(/$TFriendStatus) required=true comment="[P]ending|[A]pproved|[D]enied|[B]anned"}
+ column=DIR { type=$(/$TFriendshipReqDirection) required=true comment="[I]am|[F]riend"}
+ column=VIS { type=$(/$TFriendVisibility) required=true comment="[A]nyone|[P]ublic|[F]riend|[T]Private"}
+ column=LST { type=$(/$TFriendListIDs) required=false comment="Friend lists comma-separated"}
+
+ index=uk
+ {
+ unique=true
+ column=OWN{}
+ column=FND{}
+ }
+
+ index=friend
+ {
+ column=FND{}
+ column=OWN{}
+ }
+ }
+
+ table=NodeRating
+ {
+ comment="Rating node"
+
+ column=NOD { type=$(/$TRequiredGDIDRef) comment="A Node" }
+ column=DIM { type=$(/$TDimension) required=true comment="Dimension" }
+ column=CNT { type=$(/$TCounter) required=true default=0 comment="Count of comments (even with rating 0)" }
+ column=RTG1 { type=$(/$TCounter) required=true default=0 comment="Count rating for value 1" }
+ column=RTG2 { type=$(/$TCounter) required=true default=0 comment="Count rating for value 2" }
+ column=RTG3 { type=$(/$TCounter) required=true default=0 comment="Count rating for value 3" }
+ column=RTG4 { type=$(/$TCounter) required=true default=0 comment="Count rating for value 4" }
+ column=RTG5 { type=$(/$TCounter) required=true default=0 comment="Count rating for value 5" }
+ column=CDT { type=$(/$TTimestamp) required=true comment="The UTC date of node rating creation" }
+ column=LCD { type=$(/$TTimestamp) required=true comment="The UTC date of change rating" }
+
+ primary-key {column=NOD{} column=DIM{}}
+ }
+
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/CountFriends.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/CountFriends.mys.sql
new file mode 100644
index 0000000..78403b4
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/CountFriends.mys.sql
@@ -0,0 +1 @@
+SELECT count(*) FROM tbl_friends WHERE G_OWN = ?pG_Node
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNode.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNode.mys.sql
new file mode 100644
index 0000000..c25c46d
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNode.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_friend WHERE G_FND = ?pG_Node
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNodeAndFriend.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNodeAndFriend.mys.sql
new file mode 100644
index 0000000..951e427
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendByNodeAndFriend.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_friend SET STS = 'D', SDT = ?pdt WHERE G_OWN = ?pwon AND G_FND = ?pfnd
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendListByListId.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendListByListId.mys.sql
new file mode 100644
index 0000000..f74e111
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/DeleteFriendListByListId.mys.sql
@@ -0,0 +1,3 @@
+DELETE
+FROM tbl_friendlist
+WHERE GDID = ?pgnode AND LID = ?plistid;
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindAllFriends.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindAllFriends.mys.sql
new file mode 100644
index 0000000..160d114
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindAllFriends.mys.sql
@@ -0,0 +1 @@
+SELECT * FROM tbl_friends WHERE G_OWN = ?pNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriendListByNode.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriendListByNode.mys.sql
new file mode 100644
index 0000000..d154fbc
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriendListByNode.mys.sql
@@ -0,0 +1 @@
+SELECT * FROM tbl_friendlist WHERE G_OWN = ?pgnode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriends.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriends.mys.sql
new file mode 100644
index 0000000..6dd78ac
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindFriends.mys.sql
@@ -0,0 +1,6 @@
+SELECT *
+FROM tbl_friends
+WHERE G_OWN = ?pNode
+AND LST LIKE ?pList
+AND STS LIKE ?pStatus
+LIMIT ?pFetchStart, ?pFetchCount
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindOneFriendByNodeAndFriend.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindOneFriendByNodeAndFriend.mys.sql
new file mode 100644
index 0000000..79290cf
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/FindOneFriendByNodeAndFriend.mys.sql
@@ -0,0 +1,3 @@
+SELECT *
+FROM tbl_friend
+WHERE G_OWN = ?pown AND G_FND = ?pfnd;
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/GetNextFriend.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/GetNextFriend.mys.sql
new file mode 100644
index 0000000..7c296ec
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/GetNextFriend.mys.sql
@@ -0,0 +1 @@
+SELECT * FROM tbl_friend WHERE G_OWN = ?pNode ORDER BY CDT LIMIT ?pStart, 1
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendByNode.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendByNode.mys.sql
new file mode 100644
index 0000000..aae07df
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendByNode.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_friend WHERE G_OWN = ?gNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendListByNode.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendListByNode.mys.sql
new file mode 100644
index 0000000..d07ed10
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Friend/RemoveFriendListByNode.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_friendlist WHERE G_OWN = ?gNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscriberVolumes.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscriberVolumes.mys.sql
new file mode 100644
index 0000000..447935a
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscriberVolumes.mys.sql
@@ -0,0 +1 @@
+SELECT count(*) AS CNT FROM tbl_subvol WHERE G_OWN = ?pNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscribers.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscribers.mys.sql
new file mode 100644
index 0000000..543fde4
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/CountSubscribers.mys.sql
@@ -0,0 +1 @@
+SELECT sum(cnt) AS CNT FROM tbl_subvol WHERE G_OWN = ?pNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/DeleteOneNodeByGDID.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/DeleteOneNodeByGDID.mys.sql
new file mode 100644
index 0000000..36384cc
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/DeleteOneNodeByGDID.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_node SET IN_USE = ?pInUse WHERE GDID = ?pgnode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindOneNodeByGDID.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindOneNodeByGDID.mys.sql
new file mode 100644
index 0000000..54bff91
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindOneNodeByGDID.mys.sql
@@ -0,0 +1,11 @@
+SELECT GDID AS GDID
+, TYP AS Node_Type
+, G_OSH AS G_OriginShard
+, G_ORI AS G_Origin
+, ONM AS Origin_Name
+, ODT AS Origin_Data
+, CDT AS Create_Date
+, FVI AS Friend_Visibility
+, IN_USE AS In_Use
+FROM tbl_node
+WHERE GDID = ?pgnode AND IN_USE = 'T'
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriber.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriber.mys.sql
new file mode 100644
index 0000000..d8c0907
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriber.mys.sql
@@ -0,0 +1,8 @@
+SELECT G_VOL AS G_SubscriberVolume
+, G_SUB AS G_Subscriber
+, STP AS Subs_Type
+, CDT AS Create_Date
+, PAR AS Parameters
+FROM tbl_subscriber
+WHERE G_VOL = ?pVol
+AND G_SUB = ?pSub
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriberVolumes.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriberVolumes.mys.sql
new file mode 100644
index 0000000..42eb3ae
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscriberVolumes.mys.sql
@@ -0,0 +1,7 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_SubscriberVolume
+, CNT AS Count
+, CDT AS Create_Date
+FROM tbl_subvol
+WHERE G_OWN = ?pNode
+ORDER BY CDT
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscribers.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscribers.mys.sql
new file mode 100644
index 0000000..9694d7c
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/FindSubscribers.mys.sql
@@ -0,0 +1,9 @@
+SELECT G_VOL AS G_SubscriberVolume
+, G_SUB AS G_Subscriber
+, STP AS Subs_Type
+, CDT AS Create_Date
+, PAR AS Parameters
+FROM tbl_subscriber
+WHERE G_VOL = ?pVol
+ORDER BY G_VOL, G_SUB
+LIMIT ?pStart, ?pCount
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/GetNextVolume.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/GetNextVolume.mys.sql
new file mode 100644
index 0000000..fd3fd92
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/GetNextVolume.mys.sql
@@ -0,0 +1,8 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_SubscriberVolume
+, CNT AS Count
+, CDTCreate_Date
+FROM tbl_subvol
+WHERE G_OWN = ?pNode
+ORDER BY CDT
+LIMIT ?pStart, 1
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveNode.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveNode.mys.sql
new file mode 100644
index 0000000..7cebe3a
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveNode.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_node WHERE GDID = ?gNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubVol.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubVol.mys.sql
new file mode 100644
index 0000000..3123d31
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubVol.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_subscriber WHERE G_OWN = ?gNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubscribers.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubscribers.mys.sql
new file mode 100644
index 0000000..d5e5a87
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Node/RemoveSubscribers.mys.sql
@@ -0,0 +1 @@
+DELETE tbl_node WHERE G_VOL = ?gVol
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CancelComplaintsByComment.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CancelComplaintsByComment.mys.sql
new file mode 100644
index 0000000..83a7252
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CancelComplaintsByComment.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_complaint SET IN_USE = 'F' WHERE G_CMT = ?pG_Comment AND IN_USE = 'T'
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRating.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRating.mys.sql
new file mode 100644
index 0000000..4e325dd
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRating.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_noderating SET RTG1=0, RTG2=0, RTG3=0, RTG4=0, RTG5=0, CTD=?pCDT, WHERE G_NOD = ?pNode AND DIM = ?pDIM
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRatings.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRatings.mys.sql
new file mode 100644
index 0000000..3c82ecb
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/ClearNodeRatings.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_noderating SET RTG1=0, RTG2=0, RTG3=0, RTG4=0, RTG5=0, CTD=?pCDT, WHERE G_NOD = ?pNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CountResponses.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CountResponses.mys.sql
new file mode 100644
index 0000000..d2c9a59
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/CountResponses.mys.sql
@@ -0,0 +1 @@
+SELECT count(*) AS CNT FROM tbl_comment WHERE G_PAR = ?pComment AND G_VOL = ?pVolume
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/DeleteResponses.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/DeleteResponses.mys.sql
new file mode 100644
index 0000000..d5dc7d9
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/DeleteResponses.mys.sql
@@ -0,0 +1 @@
+DELETE FROM tbl_comment WHERE G_PAR = ?pParent AND G_VOL = ?gVolume
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentByGDID.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentByGDID.mys.sql
new file mode 100644
index 0000000..c637852
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentByGDID.mys.sql
@@ -0,0 +1,18 @@
+SELECT GDID
+, G_VOL AS G_CommentVolume
+, G_ATR AS G_AuthorNode
+, G_TRG AS G_TargetNode
+, DIM AS Dimension
+, ROOT AS IsRoot
+, G_PAR AS G_Parent
+, MSG AS Message
+, DAT AS Data
+, CDT AS Create_Date
+, LKE AS "Like"
+, DIS AS Dislike
+, CMP AS ComplaintCount
+, PST AS PublicationState
+, RTG AS Rating
+, In_Use
+, RCNT as ResponseCount
+FROM tbl_comment WHERE GDID = ?pGDID
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolume.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolume.mys.sql
new file mode 100644
index 0000000..1be6544
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolume.mys.sql
@@ -0,0 +1,7 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_CommentVolume
+, DIM AS Dimension
+, CNT AS Count
+, CDT AS Create_Date
+FROM tbl_commentvol
+WHERE G_OWN = ?pNode AND G_VOL = ?pVol
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolumes.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolumes.mys.sql
new file mode 100644
index 0000000..a6d235a
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindCommentVolumes.mys.sql
@@ -0,0 +1,6 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_CommentVolume
+, DIM AS Dimension
+, CNT AS Count
+, CDT AS Create_Date
+FROM tbl_commentvol WHERE G_OWN = ?pNode AND DIM = ?pDim AND CDT <= ?pCDT ORDER BY CDT DESC LIMIT 0,?cnt
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComments.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComments.mys.sql
new file mode 100644
index 0000000..dd76a29
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComments.mys.sql
@@ -0,0 +1,19 @@
+SELECT GDID
+, G_VOL AS G_CommentVolume
+, G_ATR AS G_AuthorNode
+, G_TRG AS G_TargetNode
+, DIM AS Dimension
+, ROOT AS IsRoot
+, G_PAR AS G_Parent
+, MSG AS Message
+, DAT AS Data
+, CDT AS Create_Date
+, LKE AS "Like"
+, DIS AS Dislike
+, CMP AS ComplaintCount
+, PST AS PublicationState
+, RTG AS Rating
+, In_Use
+, RCNT as ResponseCount
+FROM tbl_comment
+WHERE (G_TRG = ?pNode) AND (DIM = ?pDim) AND (ROOT = ?pRoot) AND (In_Use = 'T' OR RCNT > 0)
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComplaints.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComplaints.mys.sql
new file mode 100644
index 0000000..cd10c40
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindComplaints.mys.sql
@@ -0,0 +1,10 @@
+SELECT
+ G_CMT AS G_Comment,
+ GDID,
+ G_ATH AS G_AuthorNode,
+ KND AS Kind,
+ MSG AS Message,
+ CDT AS Create_Date,
+ In_Use
+FROM tbl_complaint
+WHERE G_CMT = ?pComment
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindEmptyCommentVolume.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindEmptyCommentVolume.mys.sql
new file mode 100644
index 0000000..735af61
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindEmptyCommentVolume.mys.sql
@@ -0,0 +1,9 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_CommentVolume
+, DIM AS Dimension
+, CNT AS Count
+, CDT AS Create_Date
+FROM tbl_commentvol
+WHERE G_OWN = ?pNode AND DIM = ?pDim AND CNT < ?pMaxCount
+ORDER BY CDT DESC
+LIMIT 0,1
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRating.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRating.mys.sql
new file mode 100644
index 0000000..0e9a5ac
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRating.mys.sql
@@ -0,0 +1,15 @@
+SELECT G_NOD AS G_Node
+, CDT AS Create_Date
+, DIM AS Dimension
+, CNT AS Cnt
+, LCD AS Last_Change_Date
+, RTG1 AS Rating1
+, RTG2 AS Rating2
+, RTG3 AS Rating3
+, RTG4 AS Rating4
+, RTG5 AS Rating5
+FROM tbl_noderating
+WHERE G_NOD = ?pNode
+ AND DIM = ?pDim
+ORDER BY CDT DESC
+LIMIT 1
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRatings.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRatings.mys.sql
new file mode 100644
index 0000000..38091db
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindNodeRatings.mys.sql
@@ -0,0 +1,11 @@
+SELECT G_NOD AS G_Node
+, CDT AS Create_Date
+, DIM AS Dimension
+, CNT AS Cnt
+, LCD AS Last_Change_Date
+, RTG1 AS Rating1
+, RTG2 AS Rating2
+, RTG3 AS Rating3
+, RTG4 AS Rating4
+, RTG5 AS Rating5
+FROM tbl_noderating WHERE CDT <= ?pDT AND G_NOD = ?pNode
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindResponses.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindResponses.mys.sql
new file mode 100644
index 0000000..c9973b5
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindResponses.mys.sql
@@ -0,0 +1,18 @@
+SELECT G_VOL AS G_CommentVolume
+, GDID
+, G_ATR AS G_AuthorNode
+, G_TRG AS G_TargetNode
+, DIM AS Dimension
+, ROOT AS IsRoot
+, G_PAR AS G_Parent
+, MSG AS Message
+, DAT AS Data
+, CDT AS Create_Date
+, LKE AS "Like"
+, DIS AS Dislike
+, CMP AS Complaint
+, PST AS PublicationState
+, RTG AS Rating
+, In_Use
+, RCNT as ResponseCount
+FROM tbl_comment WHERE G_PAR = ?pComment AND G_VOL = ?pVolume AND (In_Use = 'T' OR (In_Use = 'F' AND RCNT > 0))
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindVolumeComment.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindVolumeComment.mys.sql
new file mode 100644
index 0000000..735af61
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/FindVolumeComment.mys.sql
@@ -0,0 +1,9 @@
+SELECT G_OWN AS G_Owner
+, G_VOL AS G_CommentVolume
+, DIM AS Dimension
+, CNT AS Count
+, CDT AS Create_Date
+FROM tbl_commentvol
+WHERE G_OWN = ?pNode AND DIM = ?pDim AND CNT < ?pMaxCount
+ORDER BY CDT DESC
+LIMIT 0,1
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/HasCommentsCreatedByAuthor.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/HasCommentsCreatedByAuthor.mys.sql
new file mode 100644
index 0000000..c47453d
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/HasCommentsCreatedByAuthor.mys.sql
@@ -0,0 +1,5 @@
+SELECT *
+ FROM tbl_comment
+WHERE
+ DIM = ?pDim AND G_ATR = ?pAuthor AND G_TRG = ?pNode AND ROOT = 'T' AND In_Use = 'T'
+LIMIT 1
diff --git a/src/Agni.Social/Graph/Server/Data/Scripts/Rating/UpdateLike.mys.sql b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/UpdateLike.mys.sql
new file mode 100644
index 0000000..6bc7db2
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/Scripts/Rating/UpdateLike.mys.sql
@@ -0,0 +1 @@
+UPDATE tbl_comment SET LKE=LKE+?pdtLike, DIS=DIS+?pdtDislike WHERE GDID = ?pComment AND G_VOL = ?pVolume
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/Data/SubscriberRow.cs b/src/Agni.Social/Graph/Server/Data/SubscriberRow.cs
new file mode 100644
index 0000000..6987520
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/Data/SubscriberRow.cs
@@ -0,0 +1,65 @@
+using System;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+
+using Agni.Social.Graph.Server.Data.Schema;
+using NFX.Serialization.Arow;
+
+namespace Agni.Social.Graph.Server.Data
+{
+
+ ///
+ /// Logical chunk of subscribers
+ ///
+ [Table(name: "tbl_subscribervol")]
+ public sealed class SubscriberVolumeRow : BaseRow
+ {
+ /// Owner GDID; Briefcase key
+ [Field(backendName: "G_OWN", required: true, key: true)]
+ public GDID G_Owner { get; set; }
+
+ ///
+ /// Link for Subscriber Volume.
+ /// G_VOL is generated of NodeRow GDID sequence
+ ///
+ [Field(backendName: "G_VOL", required: true, key: true)]
+ public GDID G_SubscriberVolume { get; set; }
+
+ /// Count subscriber in volume
+ [Field(backendName: "CNT", required: true)]
+ public int Count { get; set; }
+
+ /// Create date volume
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+
+ }
+
+ ///
+ /// Lists all subscribers per volume; sharded in the graph area
+ ///
+ [Table(name: "tbl_subscriber")]
+ public sealed class SubscriberRow : BaseRow
+ {
+ /// Emitter - briefcase key
+ [Field(backendName: "G_VOL", required: true, key: true)]
+ public GDID G_SubscriberVolume { get; set; }
+
+ /// Subscriber
+ [Field(backendName: "G_SUB", required: true, key: true)]
+ public GDID G_Subscriber { get; set; }
+
+ /// Denormalizes node type from G_Subscriber for faster search
+ [Field(backendName: "STP", required: true, minLength: GSNodeType.MIN_LEN, maxLength: GSNodeType.MAX_LEN)]
+ public string Subs_Type { get; set; }
+
+ [Field(backendName: "CDT", required: true)]
+ public DateTime Create_Date { get; set; }
+
+ /// Subscription details
+ [Field(backendName: "PAR", required: false)]
+ public byte[] Parameters { get; set; }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/GraphCommentFetchDefaultStrategy.cs b/src/Agni.Social/Graph/Server/GraphCommentFetchDefaultStrategy.cs
new file mode 100644
index 0000000..0dcb71e
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphCommentFetchDefaultStrategy.cs
@@ -0,0 +1,142 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using NFX;
+using NFX.ApplicationModel.Pile;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX.Environment;
+
+using Agni.MDB;
+using Agni.Social.Graph.Server.Data;
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server
+{
+ public class GraphCommentFetchDefaultStrategy : DisposableObject, IConfigurable
+ {
+
+ public GraphCommentFetchDefaultStrategy(GraphSystemService graphSystemService, IConfigSectionNode config)
+ {
+ m_GraphSystemService = graphSystemService;
+ m_ConfigNode = config;
+ ConfigAttribute.Apply(this, config);
+ }
+
+ protected GraphSystemService m_GraphSystemService;
+ protected IConfigSectionNode m_ConfigNode;
+
+ protected GraphHost GraphHost { get { return GraphOperationContext.Instance.GraphHost; } }
+ protected IMDBDataStore DataStore { get { return m_GraphSystemService.DataStore; } }
+ protected ICache Cache { get { return m_GraphSystemService.Cache; } }
+ protected int MaxLastCommentVolumes { get { return GraphSystemService.MAX_SCAN_TAIL_COMMENT_VOLUMES; } }
+
+ public void Configure(IConfigSectionNode node)
+ {
+
+ }
+
+ public virtual IEnumerable Fetch(CommentQuery query)
+ {
+ var start = query.BlockIndex * CommentQuery.COMMENT_BLOCK_SIZE;
+ var comments = Cache.FetchThrough>(query,
+ SocialConsts.GS_COMMENT_BLOCK_TBL,
+ CacheParams.ReadFreshWriteSec(0), // todo TO DISCUSS
+ commQry =>
+ {
+ var rows = new List();
+ var qryVolumes = Queries.FindCommentVolumes(query.G_TargetNode, query.Dimension, MaxLastCommentVolumes, query.AsOfDate);
+ var volumes = ForNode(query.G_TargetNode).LoadEnumerable(qryVolumes);
+ foreach (var volume in volumes)
+ {
+ var qryComments = Queries.FindComments(query.G_TargetNode, query.Dimension, true);
+ var loadRows = ForComment(volume.G_CommentVolume).LoadEnumerable(qryComments);
+ rows.AddRange(loadRows);
+ }
+
+ IEnumerable orderedRows = null;
+
+ switch (query.OrderType)
+ {
+ case CommentOrderType.ByDate:
+ if (query.Ascending)
+ orderedRows = rows.OrderBy(row => row.Create_Date);
+ else
+ orderedRows = rows.OrderByDescending(row => row.Create_Date);
+ break;
+ case CommentOrderType.ByPositive:
+ orderedRows = rows.OrderBy(row => row.Rating)
+ .ThenByDescending(row => row.Create_Date);
+ break;
+ case CommentOrderType.ByNegative:
+ orderedRows = rows.OrderByDescending(row => row.Rating)
+ .ThenByDescending(row => row.Create_Date);
+ break;
+ case CommentOrderType.ByPopular:
+ if (query.Ascending)
+ orderedRows = rows.OrderBy(row => row.Like + row.Dislike)
+ .ThenByDescending(row => row.Create_Date);
+ else
+ orderedRows = rows.OrderByDescending(row => row.Like + row.Dislike)
+ .ThenByDescending(row => row.Create_Date);
+ break;
+ case CommentOrderType.ByUsefull:
+ if (query.Ascending)
+ orderedRows = rows.OrderBy(row => row.Like - row.Dislike)
+ .ThenByDescending(row => row.Create_Date);
+ else
+ orderedRows = rows.OrderByDescending(row => row.Like - row.Dislike)
+ .ThenByDescending(row => row.Create_Date);
+ break;
+ default:
+ orderedRows = rows.OrderByDescending(row => row.Create_Date);
+ break;
+ }
+ return orderedRows.Skip(start)
+ .Take(CommentQuery.COMMENT_BLOCK_SIZE)
+ .Select(RowToComment).ToArray();
+ });
+
+ return comments;
+ }
+
+ public CRUDOperations ForNode(GDID gNode)
+ {
+ return DataStore.PartitionedOperationsFor(SocialConsts.MDB_AREA_NODE, gNode);
+ }
+
+ protected CRUDOperations ForComment(GDID gVolume)
+ {
+ return DataStore.PartitionedOperationsFor(SocialConsts.MDB_AREA_COMMENT, gVolume);
+ }
+
+ public Comment RowToComment(CommentRow row)
+ {
+ if (row == null)
+ return default(Comment);
+ var commentID = new CommentID(row.G_CommentVolume, row.GDID);
+ var parentID = row.G_Parent.HasValue ? new CommentID(row.G_CommentVolume, row.G_Parent.Value) : (CommentID?)null;
+ var authorNode = m_GraphSystemService.GetNode(row.G_AuthorNode);
+ var targetNode = m_GraphSystemService.GetNode(row.G_TargetNode);
+ var editableTimespan = GraphHost.EditCommentSpan(targetNode, row.Dimension);
+ var lifeTime = App.TimeSource.UTCNow - row.Create_Date;
+
+ return new Comment(commentID,
+ parentID,
+ authorNode,
+ targetNode,
+ row.Create_Date,
+ row.Dimension,
+ GSPublicationState.ToPublicationState(row.PublicationState),
+ row.IsRoot ? (RatingValue)row.Rating : RatingValue.Undefined,
+ row.Message,
+ row.Data,
+ row.Like,
+ row.Dislike,
+ row.ComplaintCount,
+ row.ResponseCount,
+ row.In_Use,
+ editableTimespan > lifeTime);
+ }
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/GraphHost.cs b/src/Agni.Social/Graph/Server/GraphHost.cs
new file mode 100644
index 0000000..80eb607
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphHost.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+using NFX.DataAccess.CRUD;
+
+using Agni.Social.Graph.Server.Data;
+
+namespace Agni.Social.Graph.Server
+{
+ ///
+ /// Provides hosting API for the graph system in the particular business system scope
+ ///
+ public abstract class GraphHost : ApplicationComponent, IConfigurable
+ {
+
+ protected GraphHost(GraphOperationContext director, IConfigSectionNode config) : base(director)
+ {
+ ConfigAttribute.Apply(this, config);
+ }
+
+ ///
+ /// Limits the number of responses to comments
+ ///
+ public abstract int MaxResponseForComment { get; }
+
+ ///
+ /// Perform actual work of event delivery.
+ /// Returns true if event was (scheduled-to be) delivered
+ ///
+ public bool DeliverEvent(GraphNode nodeRecipient, GraphNode nodeSender, Event evt, string subscriptionParameters)
+ {
+ return DoDeliverEvent(nodeRecipient, nodeSender, evt, subscriptionParameters);
+ }
+
+ //filter event batch start
+ //filter event recipient
+ //filter event batch end
+
+ public void Configure(IConfigSectionNode node)
+ {
+ DoConfigure(node);
+ }
+
+
+ ///
+ /// Convert binary data to Row Node
+ ///
+ public abstract TypedRow NodeBinaryDataToObject(string nodeType, byte[] data);
+
+ ///
+ /// Convert Row Node to binary data
+ ///
+ public abstract byte[] ObjectToNodeBinaryData(string nodeType, TypedRow data);
+
+ ///
+ /// Filter by query
+ ///
+ public abstract IEnumerable FilterByOriginQuery(IEnumerable data, string orgQry);
+
+ ///
+ ///
+ ///
+ public abstract IEnumerable FilterEventsChunk(IEnumerable subscribersChunk, Event evt, IConfigSectionNode cfg);
+
+ ///
+ /// This function must not leak exceptions
+ /// Returns the subscriptions that could not be delivered now - the system will try to redeliver them later asynchronously
+ ///
+ public abstract IEnumerable DeliverEventsChunk(IEnumerable filtered, Event evt, IConfigSectionNode cfg);
+
+ ///
+ /// Returns true if specified node types can be friends in the specified direction.
+ /// Keep in mind that friendship is bidirectional so this method only checks the initiating party
+ ///
+ public abstract bool CanBeFriends(string fromNodeType, string toNodeType);
+
+ ///
+ /// Returns true if specified node types can be subscribed
+ ///
+ public abstract bool CanBeSubscribed(string subscriberNodeType, string emitterNodeType);
+
+ #region Graph comment
+
+ ///
+ /// Returns true if specified node type can rate other nodes
+ ///
+ public abstract bool CanBeRatingActor(string ratingNodeType, string dimension);
+
+ ///
+ /// Returns true if the specified node type requires rating for the specified dimension
+ ///
+ public abstract bool CommentRatingRequired(string ratedNodeType, string dimension);
+
+ ///
+ /// Returns a span of time within which a comment can be edited
+ ///
+ public abstract TimeSpan EditCommentSpan(GraphNode targetNode, string dimension);
+
+ ///
+ /// Returns true if authorNode can create new comment of specified targetNode as of commentDate and
+ /// if rating value can be applied
+ ///
+ public virtual bool CanCreateComment(GraphNode authorNode,
+ GraphNode targetNode,
+ string dimension,
+ DateTime commentDate,
+ RatingValue rating)
+ {
+ var ratingRequired = CommentRatingRequired(targetNode.NodeType, dimension);
+
+ if (ratingRequired && rating == RatingValue.Undefined)
+ return false;
+
+ if (ratingRequired && !CanBeRatingActor(authorNode.NodeType, dimension))
+ return false;
+
+ return DoCanCreateComment(authorNode, targetNode, dimension, commentDate, rating);
+ }
+
+ ///
+ /// Returns true if authorNode can create a response to the specified comment as of commentDate and
+ /// if rating value can be applied
+ ///
+ public virtual bool CanCreateCommentResponse(Comment parent, GraphNode authorNode, DateTime responseDate)
+ {
+ if (parent.ResponseCount > MaxResponseForComment) return false;
+
+ return DoCanCreateCommentResponse(parent, authorNode, responseDate);
+ }
+
+ ///
+ /// A hook invoked upon physical deletion of comment
+ ///
+ public abstract void DeleteComment(GraphNode authorNode, CommentID comment);
+
+ #endregion
+
+ protected abstract void DoConfigure(IConfigSectionNode node);
+
+ protected abstract bool DoDeliverEvent(GraphNode nodeRecipient, GraphNode nodeSender, Event evt, string subscriptionParameters);
+
+ protected abstract bool DoCanCreateComment(GraphNode authorNode,
+ GraphNode targetNode,
+ string dimension,
+ DateTime commentDate,
+ RatingValue rating);
+
+ protected abstract bool DoCanCreateCommentResponse(Comment parent, GraphNode authorNode, DateTime responseDate);
+ }
+}
diff --git a/src/Agni.Social/Graph/Server/GraphOperationContext.cs b/src/Agni.Social/Graph/Server/GraphOperationContext.cs
new file mode 100644
index 0000000..09848a8
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphOperationContext.cs
@@ -0,0 +1,92 @@
+using Agni.MDB;
+using Agni.WebMessaging;
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+
+namespace Agni.Social.Graph.Server
+{
+ ///
+ /// Provides context for graph operations: graph config, datastore, and host
+ ///
+ public sealed class GraphOperationContext : ApplicationComponent, IApplicationStarter, IApplicationFinishNotifiable
+ {
+
+ private static object s_Lock = new object();
+ private static volatile GraphOperationContext s_Instance;
+
+ public static GraphOperationContext Instance
+ {
+ get
+ {
+ var instance = s_Instance;
+ if (instance == null)
+ throw new GraphException(StringConsts.GS_INSTANCE_DATA_LAYER_IS_NOT_ALLOCATED_ERROR.Args(typeof(GraphSystemService).Name));
+ return instance;
+ }
+ }
+
+ private GraphOperationContext()
+ {
+ lock (s_Lock)
+ {
+ if (s_Instance != null)
+ throw new WebMessagingException(StringConsts.GS_INSTANCE_ALREADY_ALLOCATED_ERROR.Args(GetType().Name));
+ s_Instance = this;
+ }
+ }
+
+ protected override void Destructor()
+ {
+ lock (s_Lock)
+ {
+ base.Destructor();
+
+ DisposableObject.DisposeAndNull(ref m_GraphHost);
+ DisposableObject.DisposeAndNull(ref m_DataStore);
+
+ s_Instance = null;
+ }
+ }
+
+ private IConfigSectionNode m_Config;
+ private MDBDataStore m_DataStore;
+ private GraphHost m_GraphHost;
+
+ public string Name { get { return GetType().Name; } }
+ public IMDBDataStore DataStore {get { return m_DataStore; }}
+ public GraphHost GraphHost { get { return m_GraphHost; }}
+
+ bool IApplicationStarter.ApplicationStartBreakOnException { get { return true; } }
+
+ void IApplicationStarter.ApplicationStartBeforeInit(IApplication application) {}
+
+ void IApplicationStarter.ApplicationStartAfterInit(IApplication application)
+ {
+ application.RegisterAppFinishNotifiable(this);
+
+ var nDataSore = m_Config[SocialConsts.CONFIG_DATA_STORE_SECTION];
+ if(!nDataSore.Exists) throw new SocialException(StringConsts.GS_INIT_NOT_CONF_ERRROR.Args(this.GetType().Name, SocialConsts.CONFIG_DATA_STORE_SECTION));
+ m_DataStore = FactoryUtils.MakeAndConfigure(nDataSore, args: new object[] { "GraphSystem", this });
+ m_DataStore.Start();
+
+ var nHost = m_Config[SocialConsts.CONFIG_GRAPH_HOST_SECTION];
+ if (!nHost.Exists) throw new SocialException(StringConsts.GS_INIT_NOT_CONF_ERRROR.Args(this.GetType().Name, SocialConsts.CONFIG_GRAPH_HOST_SECTION));
+ m_GraphHost = FactoryUtils.MakeAndConfigure(nHost, args: new object[]{ this, nHost });
+ }
+
+ void IApplicationFinishNotifiable.ApplicationFinishBeforeCleanup(IApplication application)
+ {
+ Dispose();
+ }
+
+ void IApplicationFinishNotifiable.ApplicationFinishAfterCleanup(IApplication application) { }
+
+ void IConfigurable.Configure(IConfigSectionNode node)
+ {
+ m_Config = node;
+
+ ConfigAttribute.Apply(this, node);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphServers.cs b/src/Agni.Social/Graph/Server/GraphServers.cs
new file mode 100644
index 0000000..05ce887
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphServers.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using NFX.DataAccess.Distributed;
+
+namespace Agni.Social.Graph.Server
+{
+ public sealed class GraphNodeSystemServer : IGraphNodeSystem
+ {
+ public IGraphNodeSystem Nodes { get { return GraphSystemService.Instance; } }
+
+ public GraphChangeStatus SaveNode(GraphNode node)
+ {
+ return Nodes.SaveNode(node);
+ }
+
+ public GraphNode GetNode(GDID gNode)
+ {
+ return Nodes.GetNode(gNode);
+ }
+
+ public GraphChangeStatus DeleteNode(GDID gNode)
+ {
+ return Nodes.DeleteNode(gNode);
+ }
+
+ public GraphChangeStatus UndeleteNode(GDID gNode)
+ {
+ return Nodes.UndeleteNode(gNode);
+ }
+
+ public GraphChangeStatus RemoveNode(GDID gNode)
+ {
+ return Nodes.RemoveNode(gNode);
+ }
+ }
+
+ public sealed class GraphCommentSystemServer : IGraphCommentSystem
+ {
+ public IGraphCommentSystem Comments { get { return GraphSystemService.Instance; } }
+
+ ///
+ /// Create new comment with rating
+ ///
+ /// GDID of autor node
+ /// GDID of target node
+ /// Scope for rating
+ /// Content
+ /// Byte array of data
+ /// State of publication
+ /// star 1-2-3-4-5
+ /// Time of current action
+ /// Comment
+ public Comment Create(GDID gAuthorNode,
+ GDID gTargetNode,
+ string dimension,
+ string content,
+ byte[] data,
+ PublicationState publicationState,
+ RatingValue value = RatingValue.Undefined,
+ DateTime? timeStamp = null)
+ {
+ return Comments.Create(gAuthorNode, gTargetNode, dimension, content, data, publicationState, value, timeStamp);
+ }
+
+ ///
+ /// Make response on target commentary
+ ///
+ /// Author
+ /// Parent commentary
+ /// Content of commentary
+ /// Byte Array
+ /// New Comment
+ public Comment Respond(GDID gAuthorNode, CommentID parent, string content, byte[] data)
+ {
+ return Comments.Respond(gAuthorNode, parent, content, data);
+ }
+
+ public GraphChangeStatus Update(CommentID commentId, RatingValue value, string content, byte[] data)
+ {
+ return Comments.Update(commentId, value, content, data);
+ }
+
+ ///
+ /// Delete comment
+ ///
+ /// Existing Comment ID
+ public GraphChangeStatus DeleteComment(CommentID commentId)
+ {
+ return Comments.DeleteComment(commentId);
+ }
+
+ public GraphChangeStatus Like(CommentID commentId, int deltaLike, int deltaDislike)
+ {
+ return Comments.Like(commentId, deltaLike, deltaDislike);
+ }
+
+ public bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension)
+ {
+ return Comments.IsCommentedByAuthor(gNode, gAuthor, dimension);
+ }
+
+ public IEnumerable GetNodeSummaries(GDID gNode)
+ {
+ return Comments.GetNodeSummaries(gNode);
+ }
+
+ public IEnumerable Fetch(CommentQuery query)
+ {
+ return Comments.Fetch(query);
+ }
+
+ public IEnumerable FetchResponses(CommentID commentId)
+ {
+ return Comments.FetchResponses(commentId);
+ }
+
+ public IEnumerable FetchComplaints(CommentID commentID)
+ {
+ return Comments.FetchComplaints(commentID);
+ }
+
+ public Comment GetComment(CommentID commentId)
+ {
+ return Comments.GetComment(commentId);
+ }
+
+ public GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message)
+ {
+ return Comments.Complain(commentId, gAuthorNode, kind, message);
+ }
+
+ public GraphChangeStatus Justify(CommentID commentID)
+ {
+ return Comments.Justify(commentID);
+ }
+ }
+
+ public sealed class GraphEventSystemServer : IGraphEventSystem
+ {
+ public IGraphEventSystem Events { get { return GraphSystemService.Instance; } }
+
+ public void EmitEvent(Event evt)
+ {
+ Events.EmitEvent(evt);
+ }
+
+ public void Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters)
+ {
+ Events.Subscribe(gRecipientNode, gEmitterNode, parameters);
+ }
+
+ public void Unsubscribe(GDID gRecipientNode, GDID gEmitterNode)
+ {
+ Events.Unsubscribe(gRecipientNode, gEmitterNode);
+ }
+
+ public long EstimateSubscriberCount(GDID gEmitterNode)
+ {
+ return Events.EstimateSubscriberCount(gEmitterNode);
+ }
+
+ public IEnumerable GetSubscribers(GDID gEmitterNode, long start, int count)
+ {
+ return Events.GetSubscribers(gEmitterNode, start, count);
+ }
+ }
+
+ public sealed class GraphFriendSystemServer : IGraphFriendSystem
+ {
+ public IGraphFriendSystem Friends { get { return GraphSystemService.Instance; } }
+
+ public IEnumerable GetFriendLists(GDID gNode)
+ {
+ return Friends.GetFriendLists(gNode);
+ }
+
+ public GraphChangeStatus AddFriendList(GDID gNode, string list, string description)
+ {
+ return Friends.AddFriendList(gNode, list, description);
+ }
+
+ public GraphChangeStatus DeleteFriendList(GDID gNode, string list)
+ {
+ return Friends.DeleteFriendList(gNode, list);
+ }
+
+ public IEnumerable GetFriendConnections(FriendQuery query)
+ {
+ return Friends.GetFriendConnections(query);
+ }
+
+ public GraphChangeStatus AddFriend(GDID gNode, GDID gFriendNode, bool? approve)
+ {
+ return Friends.AddFriend(gNode, gFriendNode, approve);
+ }
+
+ public GraphChangeStatus AssignFriendLists(GDID gNode, GDID gFriendNode, string lists)
+ {
+ return Friends.AssignFriendLists(gNode, gFriendNode, lists);
+ }
+
+ public GraphChangeStatus DeleteFriend(GDID gNode, GDID gFriendNode)
+ {
+ return Friends.DeleteFriend(gNode, gFriendNode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphSystemService.Comment.cs b/src/Agni.Social/Graph/Server/GraphSystemService.Comment.cs
new file mode 100644
index 0000000..c70d6da
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphSystemService.Comment.cs
@@ -0,0 +1,655 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NFX;
+using NFX.DataAccess.Distributed;
+using NFX.Log;
+
+using Agni.MDB;
+using Agni.Social.Graph.Server.Data;
+using Agni.Social.Graph.Server.Data.Schema;
+
+namespace Agni.Social.Graph.Server
+{
+ public partial class GraphSystemService : IGraphCommentSystem
+ {
+ ///
+ /// Create new comment with rating
+ ///
+ /// GDID of autor node
+ /// GDID of target node
+ /// Scope for rating
+ /// Content
+ /// Byte array of data
+ /// State of publication
+ /// star 1-2-3-4-5
+ /// Time of current action
+ /// ID
+ Comment IGraphCommentSystem.Create(GDID gAuthorNode,
+ GDID gTargetNode,
+ string dimension,
+ string content,
+ byte[] data,
+ PublicationState publicationState,
+ RatingValue value,
+ DateTime? timeStamp)
+ {
+ try
+ {
+ // 1. Get nodes for creation new comment
+ var authorNode = DoGetNode(gAuthorNode);
+ var targetNode = DoGetNode(gTargetNode);
+ var currentDateTime = timeStamp ?? App.TimeSource.UTCNow;
+
+ if (!GraphHost.CanCreateComment(authorNode, targetNode, dimension, currentDateTime, value))
+ throw new GraphException(StringConsts.GS_CAN_NOT_CREATE_COMMENT_ERROR.Args(authorNode, targetNode, value));
+
+ return DoCreate(targetNode,
+ authorNode,
+ null, dimension,
+ content,
+ data,
+ publicationState,
+ value,
+ currentDateTime);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GraphCommentSystem.Create()", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_CREATE_COMMENT_ERROR.Args(gTargetNode, gAuthorNode), ex);
+ }
+ }
+
+ ///
+ /// Make response to target commentary
+ ///
+ /// Author
+ /// Parent commentary
+ /// Content of commentary
+ /// Byte Array
+ /// New CommentID
+ public Comment Respond(GDID gAuthorNode, CommentID parentId, string content, byte[] data)
+ {
+ try
+ {
+ if (parentId.IsZero)
+ throw new GraphException(StringConsts.GS_RESPONSE_BAD_PARENT_ID_ERROR);
+
+ var currentDateTime = App.TimeSource.UTCNow;
+
+ // Get parent comment
+ var parent = getCommentRow(parentId);
+
+ if (parent == null) throw new GraphException(StringConsts.GS_COMMENT_NOT_FOUND.Args(parentId.G_Comment));
+ if (!parent.IsRoot) throw new GraphException(StringConsts.GS_PARENT_ID_NOT_ROOT.Args(parentId.G_Comment));
+
+ var authorNode = GetNode(gAuthorNode);
+ var parentComment = GraphCommentFetchStrategy.RowToComment(parent);
+
+ if (!GraphHost.CanCreateCommentResponse(parentComment, authorNode, currentDateTime))
+ throw new GraphException(StringConsts.GS_CAN_NOT_CREATE_RESPONSE_ERROR.Args(authorNode, parentComment.TargetNode));
+
+ // Create new comment
+ return DoCreate(parentComment.TargetNode,
+ authorNode,
+ parentId,
+ parent.Dimension,
+ content,
+ data,
+ GSPublicationState.ToPublicationState(parent.PublicationState),
+ RatingValue.Undefined,
+ App.TimeSource.UTCNow);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GraphCommentSystem.Response", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_RESPONSE_COMMENT_ERROR.Args(parentId.G_Comment), ex);
+ }
+ }
+
+ ///
+ /// Updates existing rating by ID
+ ///
+ public GraphChangeStatus Update(CommentID commentId, RatingValue value, string content, byte[] data)
+ {
+ if(commentId.IsZero)
+ return GraphChangeStatus.NotFound;
+
+ try
+ {
+ // Taking comment row
+ var currentDateTime = App.TimeSource.UTCNow;
+ var ctxComment = ForComment(commentId.G_Volume); // CRUD context for comment
+ var comment = getCommentRow(commentId, ctxComment);
+ if (comment == null)
+ return GraphChangeStatus.NotFound;
+
+ var targetNode = DoGetNode(comment.G_TargetNode);
+ if ((currentDateTime - comment.Create_Date) > GraphHost.EditCommentSpan(targetNode, comment.Dimension))
+ return GraphChangeStatus.NotFound;
+
+ var ctxNode = ForNode(comment.G_TargetNode); // CRUD context for target node
+
+ // Updating fields
+ var filter = "Message,Data";
+ comment.Message = content;
+ comment.Data = data;
+
+ // if comment is root and rating value is not RatingValue.Undefined
+ // Update rating
+ if (comment.IsRoot && value != RatingValue.Undefined)
+ {
+ // Update NodeRating
+ var nodeRating = getNodeRating(comment.G_TargetNode, comment.Dimension, ctxNode);
+ nodeRating.UpdateRating((RatingValue) comment.Rating, -1);
+ nodeRating.UpdateRating(value, 1);
+ ctxNode.Update(nodeRating, filter: "Last_Change_Date,Cnt,Rating1,Rating2,Rating3,Rating4,Rating5".OnlyTheseFields());
+
+ // Update rating value of comment
+ comment.Rating = (byte) value;
+ filter = "Rating," + filter;
+ }
+ var resComment = ctxComment.Update(comment, filter: filter.OnlyTheseFields());
+ return resComment == 0 ? GraphChangeStatus.NotFound : GraphChangeStatus.Updated;
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GraphCommentSystem.Update", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_UPDATE_RATING_ERROR.Args(commentId.G_Comment), ex);
+ }
+ }
+
+ ///
+ /// Delete comment
+ ///
+ /// Existing Comment ID
+ public GraphChangeStatus DeleteComment(CommentID commentId)
+ {
+ if(commentId.IsZero)
+ return GraphChangeStatus.NotFound;
+
+ try
+ {
+ //1. Try get comment row
+ var ctxComment = ForComment(commentId.G_Volume); // CRUD context for comment
+ var comment = getCommentRow(commentId);
+ if (comment == null)
+ return GraphChangeStatus.NotFound;
+
+ var ctxNode = ForNode(comment.G_TargetNode); // CRUD context for target node
+
+ //2. Delete comment
+ comment.In_Use = false;
+
+ if (comment.IsRoot) // Only Root comments has ratings and counts in comments of target node
+ {
+ //3. If comment has rating value, decrease value of that rating from target rating node
+ var nodeRating = getNodeRating(comment.G_TargetNode, comment.Dimension, ctxNode);
+ nodeRating.UpdateCount(-1); // Update count of comments
+ nodeRating.UpdateRating((RatingValue)comment.Rating, -1); // Update ratings (if RatingValue.Undefined - nothing happens)
+ nodeRating.Last_Change_Date = App.TimeSource.UTCNow;
+ ctxNode.Update(nodeRating, filter: "Last_Change_Date,Cnt,Rating1,Rating2,Rating3,Rating4,Rating5".OnlyTheseFields());
+ }
+ // Update parent "ResponseCount" , if comment is not root
+ else if (comment.G_Parent.HasValue)
+ {
+ var parentCommendID = new CommentID(comment.G_CommentVolume, comment.G_Parent.Value);
+ var ctxParent = ForComment(parentCommendID.G_Volume);
+ var parent = getCommentRow(parentCommendID, ctxParent);
+ if (parent != null && parent.ResponseCount > 0)
+ {
+ parent.UpdateResponseCount(-1);
+ ctxParent.Update(parent, filter: "ResponseCount".OnlyTheseFields());
+ }
+ }
+
+ var res = ctxComment.Update(comment);
+ return res > 0 ? GraphChangeStatus.Deleted : GraphChangeStatus.NotFound;
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DeleteComment", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_COMMENT_ERROR.Args(commentId.G_Comment), ex);
+ }
+ }
+
+ ///
+ /// Justify moderated comment
+ ///
+ /// Existing Comment ID
+ public GraphChangeStatus Justify(CommentID commentID)
+ {
+ if(commentID.IsZero)
+ return GraphChangeStatus.NotFound;
+
+ try
+ {
+ var ctxComment = ForComment(commentID.G_Volume);
+
+ var comment = getCommentRow(commentID, ctxComment);
+ if (comment == null)
+ return GraphChangeStatus.NotFound;
+
+ var qry = Queries.CancelComplaintsByComment(commentID.G_Comment);
+ ctxComment.ExecuteWithoutFetch(qry);
+
+ comment.ComplaintCount = 0;
+ ctxComment.Update(comment, filter: "ComplaintCount".OnlyTheseFields());
+ return GraphChangeStatus.Updated;
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "Justify", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_JUSTIFY_COMMENT_ERROR.Args(commentID.G_Comment), ex);
+ }
+ }
+
+ ///
+ /// Updates likes/dislikes
+ ///
+ public GraphChangeStatus Like(CommentID messageId, int deltaLike, int deltaDislike)
+ {
+ if(messageId.IsZero) return GraphChangeStatus.NotFound;
+
+ try
+ {
+ var ctxRating = ForComment(messageId.G_Volume);
+ var qry = Queries.UpdateLike(messageId.G_Volume, messageId.G_Comment, deltaLike, deltaDislike);
+ var res = ctxRating.ExecuteWithoutFetch(qry);
+ return res==0 ? GraphChangeStatus.NotFound : GraphChangeStatus.Updated;
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "Like", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_SET_LIKE_ERROR.Args(messageId.G_Comment), ex);
+ }
+ }
+
+ ///
+ /// Check if target node has existing comment, made by author
+ ///
+ /// Target node
+ /// Author
+ /// Scope of comments
+ public bool IsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension)
+ {
+ try
+ {
+ return DoIsCommentedByAuthor(gNode, gAuthor, dimension);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DoIsCommentedByAuthor", ex.ToMessageWithType(), ex);
+ throw new GraphException("IsCommentedByAuthor gNode={0} gAuthor={1} dimension={2}".Args(gNode, gAuthor, dimension), ex);
+ }
+ }
+
+ public IEnumerable GetNodeSummaries(GDID gNode)
+ {
+ try
+ {
+ return DoGetNodeSummaries(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GetNodeSummaries", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_RATING_ERROR.Args(gNode), ex);
+ }
+ }
+
+ public IEnumerable Fetch(CommentQuery query)
+ {
+ try
+ {
+ return DoFetch(query);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "Fetch", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_FETCH_RATING_ERROR.Args(query.G_TargetNode), ex);
+ }
+ }
+
+ public IEnumerable FetchResponses(CommentID commentId)
+ {
+ try
+ {
+ return DoFetchResponses(commentId).ToArray();
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DoFetchResponses", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_FETCH_RESPONSE_ERROR.Args(commentId.G_Comment), ex);
+ }
+ }
+
+ public IEnumerable FetchComplaints(CommentID commentID)
+ {
+ try
+ {
+ return DoFetchComplaints(commentID).ToArray();
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "FetchComplaints", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_FETCH_COMPLAINTS_ERROR.Args(commentID.G_Comment), ex);
+ }
+ }
+
+ public Comment GetComment(CommentID commentId)
+ {
+ try
+ {
+ return DoGetComment(commentId);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GetComment", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_COMMENT_ERROR.Args(commentId.G_Comment), ex);
+ }
+ }
+
+ public GraphChangeStatus Complain(CommentID commentId, GDID gAuthorNode, string kind, string message)
+ {
+ if(commentId.IsZero) return GraphChangeStatus.NotFound;
+
+ try
+ {
+ var comment = getCommentRow(commentId);
+ if (comment == null) return GraphChangeStatus.NotFound;
+
+ var ctx = ForComment(commentId.G_Volume);
+ var complaintRow = new ComplaintRow(true)
+ {
+ G_Comment = commentId.G_Comment,
+ G_AuthorNode = gAuthorNode,
+ Kind = kind,
+ Message = message,
+ Create_Date = App.TimeSource.UTCNow,
+ In_Use = true
+ };
+ ctx.Insert(complaintRow);
+
+ comment.ComplaintCount++;
+ ctx.Update(comment, filter: "ComplaintCount".OnlyTheseFields());
+
+ return GraphChangeStatus.Updated;
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "Complain", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_COMPLAINT_ERROR.Args(commentId.G_Comment), ex);
+ }
+ }
+
+ #region Protected
+
+ ///
+ /// Check if target node has existing comment, made by author
+ ///
+ /// Target node
+ /// Author
+ /// Scope of comments
+ protected virtual bool DoIsCommentedByAuthor(GDID gNode, GDID gAuthor, string dimension)
+ {
+ if (gNode.IsZero) throw new GraphException("gNode.IsZero=true");
+ if (gAuthor.IsZero) throw new GraphException("gAuthor.IsZero=true");
+ if (dimension.IsNullOrWhiteSpace()) throw new GraphException("dimension=null");
+
+ var ctxNode = ForNode(gNode);
+ var qryVolumes = Queries.FindCommentVolumes(gNode, dimension, MaxScanTailCommentVolumes, App.TimeSource.UTCNow);
+ var volumes = ctxNode.LoadEnumerable(qryVolumes);
+ var hasComments = volumes.Any(v => hasAnyComments(v.G_CommentVolume, gNode, gAuthor, dimension));
+
+ return hasComments;
+ }
+
+ ///
+ /// Creating new comment
+ ///
+ /// Target node
+ /// Author node, who made comment
+ /// Parent comment id
+ /// Scope of comment
+ /// Content
+ /// Byte array of data
+ /// State of publication
+ /// star 1-2-3-4-5
+ /// Time of current action
+ /// ID
+ protected virtual Comment DoCreate(GraphNode targetNode,
+ GraphNode authorNode,
+ CommentID? parentID,
+ string dimension,
+ string content,
+ byte[] data,
+ PublicationState publicationState,
+ RatingValue value,
+ DateTime? creationTime)
+ {
+ // Create comment and, if need, volume
+ var ctxNode = ForNode(targetNode.GDID); // get shard context for target node
+ var volume = parentID.HasValue // if we have parentID, we need to place response comment in same volume as parent
+ ? getVolume(targetNode.GDID, parentID.Value.G_Volume, ctxNode)
+ : getEmptyVolume(targetNode.GDID, dimension, ctxNode);
+
+ if(volume == null)
+ throw new GraphException(StringConsts.GS_RESPONSE_VOLUME_MISSING_ERROR);
+
+ var comment = new CommentRow(true)
+ {
+ G_CommentVolume = volume.G_CommentVolume,
+ G_Parent = parentID.HasValue ? parentID.Value.G_Comment : (GDID?)null,
+ G_AuthorNode = authorNode.GDID,
+ G_TargetNode = targetNode.GDID,
+ Create_Date = creationTime ?? App.TimeSource.UTCNow,
+ Dimension = dimension,
+
+ Message = content,
+ Data = data,
+ PublicationState = GSPublicationState.ToDomainString(publicationState),
+ Rating = (byte)value,
+
+ Like = 0,
+ Dislike = 0,
+ ComplaintCount = 0,
+ ResponseCount = 0,
+
+ IsRoot = !parentID.HasValue,
+ In_Use = true
+ };
+ ForComment(volume.G_CommentVolume).Insert(comment);
+
+ // Update ratings , if comment is root
+ if (comment.IsRoot)
+ {
+ var nodeRating = getNodeRating(targetNode.GDID, dimension, ctxNode);
+ nodeRating.UpdateCount(1); // Update count of commentaries (only root commentaries counts)
+ nodeRating.UpdateRating(value, 1); //Update ratings
+ nodeRating.Last_Change_Date = App.TimeSource.UTCNow; // update change time
+ ctxNode.Update(nodeRating, filter: "Last_Change_Date,Cnt,Rating1,Rating2,Rating3,Rating4,Rating5".OnlyTheseFields());
+ }
+ // Update parent ResponseCount, if comment is not root
+ else if(parentID.HasValue)
+ {
+ var ctxParent = ForComment(parentID.Value.G_Volume);
+ var parent = getCommentRow(parentID.Value, ctxParent);
+ if (parent != null)
+ {
+ parent.UpdateResponseCount(1);
+ ctxParent.Update(parent, filter: "ResponseCount".OnlyTheseFields());
+ }
+ }
+
+ // Update count of commentaries in volume
+ volume.Count++;
+ ctxNode.Update(volume, filter: "Count".OnlyTheseFields());
+ return Comment.MakeNew(new CommentID(comment.G_CommentVolume, comment.GDID),
+ parentID,
+ authorNode,
+ targetNode,
+ comment.Create_Date,
+ comment.Dimension,
+ GSPublicationState.ToPublicationState(comment.PublicationState),
+ (RatingValue)comment.Rating,
+ comment.Message,
+ comment.Data);
+ }
+
+ protected virtual IEnumerable DoGetNodeSummaries(GDID gNode)
+ {
+ var qry = Queries.FindNodeRatings(gNode, App.TimeSource.UTCNow);
+ var rows = ForNode(gNode).LoadEnumerable(qry);
+ return rows.Select(row => new SummaryRating(row.G_Node,
+ row.Create_Date,
+ row.Last_Change_Date,
+ row.Dimension,
+ row.Cnt,
+ row.Rating1,
+ row.Rating2,
+ row.Rating3,
+ row.Rating4,
+ row.Rating5)).ToArray();
+ }
+
+ protected virtual IEnumerable DoFetch(CommentQuery query)
+ {
+ return GraphCommentFetchStrategy.Fetch(query);
+ }
+
+ protected virtual IEnumerable DoFetchResponses(CommentID commentId, int offset = 0, int limit = 1) // todo доделать
+ {
+ var qryResponses = Queries.FindResponses(commentId);
+ var responses = ForComment(commentId.G_Volume).LoadEnumerable(qryResponses);
+ return responses.Where(r => r.In_Use || (!r.In_Use && r.ResponseCount > 0)).Select(GraphCommentFetchStrategy.RowToComment);
+ }
+
+ protected IEnumerable DoFetchComplaints(CommentID commentID)
+ {
+ var qryComplaints = Queries.FindComplaints(commentID.G_Comment);
+ var complaints = ForComment(commentID.G_Volume).LoadEnumerable(qryComplaints);
+ return complaints.Select(c => rowToComplaint(commentID, c));
+ }
+
+ protected CRUDOperations ForComment(GDID gVolume)
+ {
+ return DataStore.PartitionedOperationsFor(SocialConsts.MDB_AREA_COMMENT, gVolume);
+ }
+
+ #endregion
+
+ #region .pvt
+
+ ///
+ /// Get existing not full Comment Volume or create a new one
+ ///
+ /// TargetNode context
+ /// TargetNode GDID
+ /// Scope of comment
+ /// Existing or new volume
+ private CommentVolumeRow getEmptyVolume(GDID gTargetNode, string dimension, CRUDOperations? ctxNode = null)
+ {
+ var ctx = ctxNode ?? ForNode(gTargetNode);
+ var qryVolume = Queries.FindEmptyCommentVolume(gTargetNode, dimension, MaxSizeCommentVolume);
+ var volume = ctxNode.LoadRow(qryVolume);
+ if (volume == null)
+ {
+ volume = new CommentVolumeRow
+ {
+ G_Owner = gTargetNode,
+ G_CommentVolume = NodeRow.GenerateNewNodeRowGDID(),
+ Dimension = dimension,
+ Count = 0,
+ Create_Date = App.TimeSource.UTCNow
+ };
+ ctx.Insert(volume);
+ }
+ return volume;
+ }
+
+ ///
+ /// Get existing CommentVolume by TargetNode and Volume
+ ///
+ /// TargetNode context
+ /// TargetNode GDID
+ /// Volume GDID
+ /// Existing volume
+ private CommentVolumeRow getVolume(GDID gTargetNode, GDID gVolume, CRUDOperations? ctxNode = null)
+ {
+ var ctx = ctxNode ?? ForNode(gTargetNode);
+ var qryVolume = Queries.FindCommentVolume(gTargetNode, gVolume);
+ return ctx.LoadRow(qryVolume);
+ }
+
+ ///
+ /// Get existing NodeRating or create a new one
+ ///
+ /// TargetNode context
+ /// TargetNode GDID
+ /// Scope of comment
+ /// Existing or new NodeRating
+ private NodeRatingRow getNodeRating(GDID gTargetNode, string dimension, CRUDOperations? ctxNode = null)
+ {
+ var ctx = ctxNode ?? ForNode(gTargetNode);
+ var qryNodeRating = Queries.FindNodeRating(gTargetNode, dimension);
+ var nodeRating = ctxNode.LoadRow(qryNodeRating);
+ if(nodeRating == null)
+ {
+ nodeRating = new NodeRatingRow
+ {
+ G_Node = gTargetNode,
+ Create_Date = App.TimeSource.UTCNow,
+ Last_Change_Date = App.TimeSource.UTCNow,
+ Dimension = dimension,
+ Rating1 = 0,
+ Rating2 = 0,
+ Rating3 = 0,
+ Rating4 = 0,
+ Rating5 = 0,
+ Cnt = 0
+ };
+ ctx.Insert(nodeRating);
+ }
+ return nodeRating;
+ }
+
+ private Comment DoGetComment(CommentID commentId)
+ {
+ var qry = Queries.FindCommentByGDID(commentId.G_Comment);
+ var row = ForComment(commentId.G_Volume).LoadRow(qry);
+ if (row == null) throw new GraphException("Comment not found, CommendID = {0}".Args(commentId));
+ return GraphCommentFetchStrategy.RowToComment(row);
+ }
+
+ private CommentRow getCommentRow(CommentID commentId, CRUDOperations? ctxComment = null)
+ {
+ if (commentId.IsZero) return null;
+ var ctx = ctxComment ?? ForComment(commentId.G_Volume);
+ var qryComment = Queries.FindCommentByGDID(commentId.G_Comment);
+ return ctx.LoadRow(qryComment);
+ }
+
+ private bool hasAnyComments(GDID gVolume, GDID gNode, GDID gAuthor, string dimension)
+ {
+ var qryHasComments = Queries.HasCommentsCreatedByAuthor(gNode, gAuthor, dimension);
+ var row = ForComment(gVolume).LoadOneRow(qryHasComments);
+ return row != null;
+ }
+
+ private Complaint rowToComplaint(CommentID commentID, ComplaintRow row)
+ {
+ if (row == null) return default(Complaint);
+
+ return new Complaint(new CommentID(commentID.G_Volume, row.G_Comment),
+ row.GDID,
+ GetNode(row.G_AuthorNode),
+ row.Kind,
+ row.Message,
+ row.Create_Date,
+ row.In_Use);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphSystemService.Event.cs b/src/Agni.Social/Graph/Server/GraphSystemService.Event.cs
new file mode 100644
index 0000000..5d70210
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphSystemService.Event.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX.Environment;
+using NFX.Log;
+using NFX.Serialization.JSON;
+
+using Agni.Social.Graph.Server.Data;
+using Agni.Social.Graph.Server.Workers;
+using Agni.Workers;
+
+namespace Agni.Social.Graph.Server
+{
+ public partial class GraphSystemService
+ {
+ ///
+ /// Emits the event - notifies all subscribers (watchers, friends etc.) about the event.
+ /// The physical notification happens via IGraphHost implementation
+ ///
+ void IGraphEventSystem.EmitEvent(Event evt)
+ {
+ try
+ {
+ IConfigSectionNode cfg = null;
+ if (evt.Config.IsNotNullOrWhiteSpace()) cfg = evt.Config.AsLaconicConfig(handling: ConvertErrorHandling.Throw);
+ DoEmitEvent(evt);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "EmitEvent", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_EMIT_EVENT_ERROR.Args(evt.ToJSON()), ex);
+ }
+ }
+
+ ///
+ /// Subscribes recipient node to the emitter node. Unlike friends the susbscription connection is uni-directional
+ ///
+ void IGraphEventSystem.Subscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters)
+ {
+ try
+ {
+ DoSubscribe(gRecipientNode, gEmitterNode, parameters);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "Subscribe", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_SUBSCRIBE_ERROR.Args(gRecipientNode.ToString(), gEmitterNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Removes the subscription. Unlike friends the subscription connection is uni-directional
+ ///
+ void IGraphEventSystem.Unsubscribe(GDID gRecipientNode, GDID gEmitterNode)
+ {
+ try
+ {
+ DoUnsubscribe(gRecipientNode, gEmitterNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DoUnsubscribe", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_UNSUBSCRIBE_ERROR.Args(gRecipientNode.ToString(), gEmitterNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Returns an estimated approximate number of subscribers that an emitter has
+ ///
+ long IGraphEventSystem.EstimateSubscriberCount(GDID gEmitterNode)
+ {
+ try
+ {
+ return DoEstimateSubscriberCount(gEmitterNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error,"EstimateSubscriberCount", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_ESTIMATE_SUBSCRIPTION_COUNT_ERROR.Args(gEmitterNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Returns Subscribers for Emitter from start position
+ ///
+ IEnumerable IGraphEventSystem.GetSubscribers(GDID gEmitterNode, long start, int count)
+ {
+ try
+ {
+ return DoGetSubscribers(gEmitterNode, start, count);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error,"GetSubscribers", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_SUBSCRIBER_ERROR.Args(gEmitterNode.ToString()), ex);
+ }
+ }
+
+ protected virtual void DoEmitEvent(Event evt)
+ {
+ DoGetNode(evt.G_EmitterNode); // Don't run todo if Emitter does not exist
+ var qryVol = Queries.CountSubscriberVolumes(evt.G_EmitterNode);
+ var countVol = ForNode(evt.G_EmitterNode).LoadRow(qryVol)["CNT"].AsInt();
+ var count = countVol < EventDeliveryCohortSize ? countVol : EventDeliveryCohortSize;
+ var todos = new List();
+ for(int i=0; i < count; i++)
+ {
+ var todo = Todo.MakeNew();
+ todo.Event = evt;
+ todo.VolumeWorkerOffset = count;
+ todo.VolumeIndex = i;
+ todo.ChunkIndex = 0;
+ todo.G_Volume = GDID.Zero;
+ todos.Add(todo);
+ }
+ SocialGraphTodos.EnqueueSubscribtion(todos);
+ }
+
+ protected virtual void DoSubscribe(GDID gRecipientNode, GDID gEmitterNode, byte[] parameters)
+ {
+ var emitter = DoGetNode(gEmitterNode);
+ var gh = DoGetNode(gRecipientNode);
+ if (!GraphHost.CanBeSubscribed(gh.NodeType, emitter.NodeType)) throw new GraphException(StringConsts.GS_CAN_NOT_BE_SUSBCRIBED_ERROR.Args(gh.NodeType, emitter.NodeType));
+ var todo = Todo.MakeNew();
+ todo.G_Owner = gEmitterNode;
+ todo.G_Subscriber = gRecipientNode;
+ todo.Subs_Type = gh.NodeType;
+ todo.Parameters = parameters;
+ SocialGraphTodos.EnqueueSubscribtion(todo);
+ }
+
+ protected virtual void DoUnsubscribe(GDID gRecipientNode, GDID gEmitterNode)
+ {
+ var todo =Todo.MakeNew();
+ todo.G_Owner = gEmitterNode;
+ todo.G_Subscriber = gRecipientNode;
+ SocialGraphTodos.EnqueueSubscribtion(todo);
+ }
+
+ protected virtual long DoEstimateSubscriberCount(GDID gEmitterNode)
+ {
+ var count = ForNode(gEmitterNode).LoadOneRow(Queries.CountSubscribers(gEmitterNode));
+ return count[0].AsLong();
+ }
+
+ protected virtual IEnumerable DoGetSubscribers(GDID gEmitterNode, long start, int count)
+ {
+ var qryVol = Queries.FindSubscriberVolumes(gEmitterNode);
+ IEnumerable volumes = ForNode(gEmitterNode).LoadEnumerable(qryVol);
+ var sum = 0;
+ SubscriberVolumeRow volume = null;
+ var i = 0;
+ foreach (var vol in volumes)
+ {
+ if (sum <= start && start <= sum + SocialConsts.GetVolumeMaxCountForPosition(i++))
+ {
+ volume = vol;
+ break;
+ }
+ sum += vol.Count;
+ }
+ if(volume == null) yield break;
+ var _start = start - sum;
+ var qry = Queries.FindSubscribers(volume.G_SubscriberVolume, _start, count);
+ var subscribers = ForNode(volume.G_SubscriberVolume).LoadEnumerable(qry);
+ foreach (var subscriber in subscribers)
+ {
+ yield return DoGetNode(subscriber.G_Subscriber);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphSystemService.Friend.cs b/src/Agni.Social/Graph/Server/GraphSystemService.Friend.cs
new file mode 100644
index 0000000..c96a9f1
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphSystemService.Friend.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Collections.Generic;
+
+using NFX;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX.Log;
+
+using Agni.Social.Graph.Server.Data;
+using Agni.Social.Graph.Server.Data.Schema;
+
+
+namespace Agni.Social.Graph.Server
+{
+ public partial class GraphSystemService
+ {
+ ///
+ /// Returns an enumeration of friend list ids for the particular node
+ ///
+ IEnumerable IGraphFriendSystem.GetFriendLists(GDID gNode)
+ {
+ try
+ {
+ return DoGetFriendLists(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GetFriendLists", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_FRIEND_LISTS_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Adds a new friend list id for the particular node. The list id may not contain commas
+ ///
+ GraphChangeStatus IGraphFriendSystem.AddFriendList(GDID gNode, string list, string description)
+ {
+ try
+ {
+ return DoAddFriendList(gNode, list, description);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "AddFriendList", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_ADD_FRIEND_LIST_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Removes friend list id for the particular node
+ ///
+ GraphChangeStatus IGraphFriendSystem.DeleteFriendList(GDID gNode, string list)
+ {
+ try
+ {
+ return DoDeleteFriendList(gNode, list);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DeleteFriendList", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_FRIEND_LIST_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Returns an enumeration of FriendConnection{GraphNode, approve date, direction, groups}
+ ///
+ IEnumerable IGraphFriendSystem.GetFriendConnections(FriendQuery query)
+ {
+ try
+ {
+ return DoGetFriendConnections(query);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GetFriendConnections", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_FRIEND_CONNECTIONS_ERROR.Args(query.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Adds a bidirectional friend connection between gNode and gFriendNode
+ /// If friend connection already exists updates the approve/ban stamp by the receiving party (otherwise approve is ignored)
+ /// If approve==null then no stamps are set, if true connection is approved given that gNode is not the one who initiated the connection,
+ /// false then connection is banned given that gNode is not the one who initiated the connection
+ ///
+ GraphChangeStatus IGraphFriendSystem.AddFriend(GDID gNode, GDID gFriendNode, bool? approve)
+ {
+ try
+ {
+ return DoAddFriend(gNode, gFriendNode, approve);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "AddFriend", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_ADD_FRIEND_ERROR.Args(gNode.ToString(), gFriendNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Assigns lists to the gNode (the operation is unidirectional - it only assigns the lists on the gNode).
+ /// Lists is a comma-separated list of friend list ids
+ ///
+ GraphChangeStatus IGraphFriendSystem.AssignFriendLists(GDID gNode, GDID gFriendNode, string lists)
+ {
+ try
+ {
+ return DoAssignFriendLists(gNode, gFriendNode, lists);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "AssignFriendLists", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_ASSIGN_FRIEND_LISTS_ERROR.Args(gNode.ToString(), gFriendNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Deletes friend connections. The operation drops both connections from node and friend
+ ///
+ GraphChangeStatus IGraphFriendSystem.DeleteFriend(GDID gNode, GDID gFriendNode)
+ {
+ try
+ {
+ return DoDeleteFriend(gNode, gFriendNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DeleteFriend", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_FRIEND_ERROR.Args(gNode.ToString(), gFriendNode.ToString()), ex);
+ }
+ }
+
+ protected virtual IEnumerable DoGetFriendLists(GDID gNode)
+ {
+ DoGetNode(gNode);
+ var rows = ForNode(gNode).LoadEnumerable(Queries.FindFriendListByNode(gNode));
+ foreach (var row in rows)
+ {
+ yield return row.List_ID;
+ }
+ }
+
+ protected virtual GraphChangeStatus DoAddFriendList(GDID gNode, string list, string description)
+ {
+ DoGetNode(gNode);
+ var row = new FriendListRow(true)
+ {
+ G_Owner = gNode,
+ List_ID = list,
+ List_Description = description,
+ Create_Date = App.TimeSource.UTCNow
+ };
+ ForNode(gNode).Insert(row);
+ return GraphChangeStatus.Added;
+ }
+
+ protected virtual GraphChangeStatus DoDeleteFriendList(GDID gNode, string list)
+ {
+ ForNode(gNode).ExecuteWithoutFetch(Queries.DeleteFriendListByListId(gNode, list));
+ return GraphChangeStatus.Deleted;
+ }
+
+ protected virtual IEnumerable DoGetFriendConnections(FriendQuery query, ICacheParams cacheParams = null)
+ {
+ var rows = ForNode(query.G_Node).LoadEnumerable(Queries.FindFriends(query));
+ foreach (var row in rows)
+ {
+ var friendNode = DoGetNode(row.G_Friend, cacheParams);
+ foreach (var graphNode in GraphHost.FilterByOriginQuery(new[] {friendNode}, query.OriginQuery))
+ {
+ yield return new FriendConnection(graphNode,
+ row.Request_Date,
+ FriendStatus.Approved.Equals(GSFriendStatus.ToFriendStatus(row.Status))
+ ? (DateTime?) row.Status_Date
+ : null,
+ GSFriendshipRequestDirection.ToFriendshipRequestDirection(row.Direction),
+ GSFriendVisibility.ToFriendVisibility(row.Visibility),
+ row.Lists);
+ }
+ }
+ }
+
+ protected virtual GraphChangeStatus DoAddFriend(GDID gNode, GDID gFriendNode, bool? approve)
+ {
+ var node = DoGetNode(gNode);
+ var friendNode = DoGetNode(gFriendNode);
+
+ if(!GraphHost.CanBeFriends(node.NodeType, friendNode.NodeType)) throw new GraphException(StringConsts.GS_FRIEND_DRIECTION_ERROR.Args(node.NodeType, friendNode.NodeType));
+
+ int countMe = countFriends(gNode);
+ int countFriend = countFriends(gFriendNode);
+
+ if (countMe > SocialConsts.GS_MAX_FRIENDS_COUNT) throw new GraphException(StringConsts.GS_MAX_FRIENDS_IN_NODE.Args(gNode));
+ if (countFriend > SocialConsts.GS_MAX_FRIENDS_COUNT) throw new GraphException(StringConsts.GS_MAX_FRIENDS_IN_NODE.Args(gFriendNode));
+
+
+ GraphChangeStatus resultMe = addFriend(gNode, gFriendNode, approve, FriendshipRequestDirection.I);
+ GraphChangeStatus resultFriend = addFriend(gFriendNode, gNode, approve, FriendshipRequestDirection.Friend);
+
+ return resultMe == GraphChangeStatus.Added && resultFriend == GraphChangeStatus.Added ? GraphChangeStatus.Added : GraphChangeStatus.Updated;
+ }
+
+ protected virtual GraphChangeStatus DoAssignFriendLists(GDID gNode, GDID gFriendNode, string lists)
+ {
+ DoGetNode(gNode);
+ DoGetNode(gFriendNode);
+
+ var ctx = ForNode(gNode);
+ var row = ctx.LoadRow(Queries.FindOneFriendByNodeAndFriend(gNode, gFriendNode));
+ if (row == null) return GraphChangeStatus.NotFound;
+ row.Lists = addToList(row.Lists, lists);
+ ctx.Update(row);
+ return GraphChangeStatus.Updated;
+ }
+
+ protected virtual GraphChangeStatus DoDeleteFriend(GDID gNode, GDID gFriendNode)
+ {
+ ForNode(gNode).ExecuteWithoutFetch(Queries.DeleteFriendByNodeAndFriend(gNode, gFriendNode));
+ ForNode(gFriendNode).ExecuteWithoutFetch(Queries.DeleteFriendByNodeAndFriend(gFriendNode, gNode));
+ return GraphChangeStatus.Deleted;
+ }
+
+
+ private GraphChangeStatus addFriend(GDID gNode, GDID gFriendNode, bool? approve, FriendshipRequestDirection direction)
+ {
+ GraphChangeStatus result;
+ var ctx = ForNode(gNode);
+ var row = ctx.LoadRow(Queries.FindOneFriendByNodeAndFriend(gNode, gFriendNode));
+ if (row != null)
+ {
+ if (approve == null) return GraphChangeStatus.Updated;
+ row.Status = approve.Value ? GSFriendStatus.APPROVED : GSFriendStatus.DENIED ;
+ row.Status_Date = App.TimeSource.UTCNow;
+ ctx.Update(row);
+ result = GraphChangeStatus.Updated;
+ }
+ else
+ {
+ row = new FriendRow(true)
+ {
+ G_Owner = gNode,
+ G_Friend = gFriendNode,
+ Status_Date = App.TimeSource.UTCNow,
+ Status = approve!= null && approve.Value ? GSFriendStatus.APPROVED: GSFriendStatus.PENDING,
+ Visibility = GSFriendVisibility.FRIENDS,
+ Request_Date = App.TimeSource.UTCNow,
+ Direction = GSFriendshipRequestDirection.ToDomainString(direction)
+ };
+ ctx.Insert(row);
+ result = GraphChangeStatus.Added;
+ }
+ return result;
+ }
+
+ private string addToList(string rowLists, string lists)
+ {
+ HashSet oldList = new HashSet(rowLists.Split(SocialConsts.GS_FRIEND_LIST_SEPARATOR.ToCharArray()));
+ lists.Split(SocialConsts.GS_FRIEND_LIST_SEPARATOR.ToCharArray()).ForEach(s => oldList.Add(s));
+ return string.Join(SocialConsts.GS_FRIEND_LIST_SEPARATOR, oldList);
+ }
+
+ private int countFriends(GDID gNode)
+ {
+ var count = ForNode(gNode).LoadOneRow(Queries.CountFriends(gNode));
+ return count[0].AsInt();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphSystemService.Node.cs b/src/Agni.Social/Graph/Server/GraphSystemService.Node.cs
new file mode 100644
index 0000000..00afe04
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphSystemService.Node.cs
@@ -0,0 +1,191 @@
+using System;
+
+using NFX;
+using NFX.ApplicationModel.Pile;
+using NFX.DataAccess.CRUD;
+using NFX.DataAccess.Distributed;
+using NFX.Log;
+using NFX.Serialization.JSON;
+
+using Agni.Social.Graph.Server.Data;
+using Agni.Social.Graph.Server.Data.Schema;
+using Agni.Social.Graph.Server.Workers;
+
+namespace Agni.Social.Graph.Server
+{
+ public partial class GraphSystemService
+ {
+ ///
+ /// Saves the GraphNode instances into the system.
+ /// If a node with such ID already exists, updates it, otherwise creates new node
+ ///
+ public GraphChangeStatus SaveNode(GraphNode node)
+ {
+ try
+ {
+ return DoSaveNode(node);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "SaveNode", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_SAVE_NODE_ERROR.Args(node.ToJSON()), ex);
+ }
+ }
+
+ ///
+ /// Fetches the GraphNode by its unique GDID or null if not found
+ ///
+ public GraphNode GetNode(GDID gNode)
+ {
+ try
+ {
+ return DoGetNode(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "GetNode", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_GET_NODE_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Deletes node by GDID
+ ///
+ public GraphChangeStatus DeleteNode(GDID gNode)
+ {
+ try
+ {
+ return DoDeleteNode(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "DeleteNode", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_NODE_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Undeletes node by GDID
+ ///
+ public GraphChangeStatus UndeleteNode(GDID gNode)
+ {
+ try
+ {
+ return DoUndeleteNode(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "UndeleteNode", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_NODE_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+ ///
+ /// Remove node by GDID from Databases
+ ///
+ public GraphChangeStatus RemoveNode(GDID gNode)
+ {
+ try
+ {
+ return DoRemoveNode(gNode);
+ }
+ catch (Exception ex)
+ {
+ Log(MessageType.Error, "RemoveNode", ex.ToMessageWithType(), ex);
+ throw new GraphException(StringConsts.GS_DELETE_NODE_ERROR.Args(gNode.ToString()), ex);
+ }
+ }
+
+
+ protected virtual GraphChangeStatus DoRemoveNode(GDID gNode)
+ {
+ var removeFriendsTodo = new EventRemoveFriendsTodo()
+ {
+ G_Node = gNode,
+ FriendIndex = 0,
+ G_Friend = GDID.Zero
+ };
+ SocialGraphTodos.EnqueueRemove(removeFriendsTodo);
+
+ var removeNodeTodo = new EventRemoveNodeTodo()
+ {
+ G_Node = gNode,
+ VolumeIndex = 0,
+ G_Volume = GDID.Zero
+ };
+ SocialGraphTodos.EnqueueRemove(removeNodeTodo);
+
+ return GraphChangeStatus.Unassigned;
+ }
+
+ private GraphChangeStatus DoUndeleteNode(GDID gNode)
+ {
+ var qry = Queries.ChangeInUseNodeByGDID(gNode, isDel:false);
+ var affected = ForNode(gNode).ExecuteWithoutFetch(qry);
+ return (affected>0) ? GraphChangeStatus.Updated : GraphChangeStatus.NotFound;
+ }
+
+ protected virtual GraphChangeStatus DoSaveNode(GraphNode node)
+ {
+ GraphChangeStatus result;
+
+ var row = loadNodeRow(node.GDID);
+ if (row == null)
+ {
+ row = new NodeRow(false)
+ {
+ GDID = node.GDID,
+ In_Use = true,
+ Node_Type = node.NodeType,
+ G_OriginShard = node.G_OriginShard,
+ G_Origin = node.G_Origin,
+ Create_Date = node.TimestampUTC
+ };
+ result = GraphChangeStatus.Added;
+ }
+ else
+ result = GraphChangeStatus.Updated;
+
+ row.Origin_Name = node.OriginName;
+ row.Friend_Visibility = GSFriendVisibility.ToDomainString(node.DefaultFriendVisibility);
+
+ ForNode(node.GDID).Upsert(row);
+
+ return result;
+ }
+
+ protected virtual GraphNode DoGetNode(GDID gNode, ICacheParams cacheParams = null)
+ {
+ var row = Cache.FetchThrough(gNode,
+ SocialConsts.GS_NODE_TBL,
+ cacheParams,
+ gdid => loadNodeRow(gNode));
+
+ if (row == null) return new GraphNode();
+
+ return new GraphNode(row.Node_Type,
+ row.GDID,
+ row.G_OriginShard,
+ row.G_Origin,
+ row.Origin_Name,
+ row.Origin_Data,
+ row.Create_Date.Value,
+ GSFriendVisibility.ToFriendVisibility(row.Friend_Visibility));
+ }
+
+ protected virtual GraphChangeStatus DoDeleteNode(GDID gNode)
+ {
+ var qry = Queries.ChangeInUseNodeByGDID(gNode, true);
+ var affected = ForNode(gNode).ExecuteWithoutFetch(qry);
+ return (affected>0) ? GraphChangeStatus.Deleted : GraphChangeStatus.NotFound;
+ }
+
+
+
+ private NodeRow loadNodeRow(GDID gNode)
+ {
+ return ForNode(gNode).LoadRow(Queries.FindOneNodeByGDID(gNode));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Agni.Social/Graph/Server/GraphSystemService.cs b/src/Agni.Social/Graph/Server/GraphSystemService.cs
new file mode 100644
index 0000000..df5917c
--- /dev/null
+++ b/src/Agni.Social/Graph/Server/GraphSystemService.cs
@@ -0,0 +1,157 @@
+using System;
+
+using NFX;
+using NFX.ApplicationModel.Pile;
+using NFX.DataAccess;
+using NFX.DataAccess.Distributed;
+using NFX.Environment;
+using NFX.Log;
+using NFX.ServiceModel;
+
+using Agni.MDB;
+using Agni.WebMessaging;
+
+namespace Agni.Social.Graph.Server
+{
+ public partial class GraphSystemService : ServiceWithInstrumentationBase