[New Feature] Apply graph strategy

This commit is contained in:
Qolzam
2018-01-12 13:40:59 +07:00
parent 7231c59f92
commit e38dbc9fd3
169 changed files with 2931 additions and 2580 deletions

View File

@@ -1,9 +1,19 @@
{
"hosting": {
"public": "public",
"rewrites": [{
"source": "**",
"destination": "/index.html"
}]
"rewrites": [
{
"source": "/bundle-v0.4.js",
"destination": "/bundle-v0.4.js"
},
{
"source": "/favicon.ico",
"destination": "/favicon.ico"
},
{
"source": "**",
"destination": "/index.html"
}
]
}
}

View File

@@ -17,7 +17,7 @@
"@types/react-infinite-scroller": "^1.0.4",
"amazon-cognito-identity-js": "^1.21.0",
"aws-sdk": "^2.132.0",
"axios": "^0.16.1",
"axios": "^0.16.2",
"babel-runtime": "^6.26.0",
"classnames": "^2.2.5",
"crypto-js": "^3.1.9-1",
@@ -28,7 +28,8 @@
"faker": "^4.1.0",
"file-loader": "^0.11.1",
"firebase": "^4.6.2",
"inversify": "^4.3.0",
"install": "^0.10.2",
"inversify": "^4.6.0",
"keycode": "^2.1.9",
"lodash": "^4.17.4",
"material-ui": "^0.19.4",
@@ -36,6 +37,7 @@
"morgan": "^1.8.1",
"node-env-file": "^0.1.8",
"node-sass": "^4.5.2",
"npm": "^5.6.0",
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-addons-test-utils": "^15.6.2",
@@ -43,7 +45,7 @@
"react-dom": "^16.0.0",
"react-event-listener": "^0.5.1",
"react-hot-loader": "^3.1.3",
"react-infinite-scroller": "^1.1.1",
"react-infinite-scroller": "^1.1.2",
"react-linkify": "^0.2.1",
"react-parallax": "^1.4.4",
"react-redux": "^5.0.6",

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -122,7 +122,7 @@
</div>
</div>
<script src="/bundle-v0.3.js"></script>
<script src="/bundle-v0.5.js"></script>
</body>
</html>

View File

@@ -15,13 +15,16 @@ import { AuthorizeActionType } from 'constants/authorizeActionType'
// - Import services
import { IAuthorizeService } from 'core/services/authorize'
import { IServiceProvider, ServiceProvide } from 'core/factories'
// - Import actions
import * as globalActions from 'actions/globalActions'
import { provider } from '../socialEngine'
import { SocialProviderTypes } from 'core/socialProviderTypes'
const serviceProvider: IServiceProvider = new ServiceProvide()
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
/**
* Get service providers
*/
const authorizeService: IAuthorizeService = provider.get<IAuthorizeService>(SocialProviderTypes.AuthorizeService)
/* _____________ CRUD DB _____________ */

View File

@@ -1,6 +1,6 @@
// - Import domain
import { User } from 'core/domain/users'
import { Circle, UserFollower } from 'core/domain/circles'
import { User, Profile } from 'core/domain/users'
import { Circle, UserTie } from 'core/domain/circles'
import { SocialError } from 'core/domain/common'
// - Import utility components
@@ -15,11 +15,17 @@ import * as postActions from 'actions/postActions'
import * as userActions from 'actions/userActions'
import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider,ServiceProvide } from 'core/factories'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICircleService } from 'core/services/circles'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
import { IUserTieService } from 'core/services/circles'
const serviceProvider: IServiceProvider = new ServiceProvide()
const circleService: ICircleService = serviceProvider.createCircleService()
/**
* Get service providers
*/
const circleService: ICircleService = provider.get<ICircleService>(SocialProviderTypes.CircleService)
const userTieService: IUserTieService = provider.get<IUserTieService>(SocialProviderTypes.UserTieService)
/* _____________ CRUD DB _____________ */
@@ -33,10 +39,9 @@ export let dbAddCircle = (circleName: string) => {
let uid: string = getState().authorize.uid
let circle: Circle = {
creationDate: moment().unix(),
name: circleName,
users: {}
name: circleName
}
return circleService.addCircle(uid,circle).then((circleKey: string) => {
return circleService.addCircle(uid, circle).then((circleKey: string) => {
circle.id = circleKey
circle.ownerId = uid
dispatch(addCircle(circle))
@@ -47,31 +52,29 @@ export let dbAddCircle = (circleName: string) => {
}
/**
* Add a user in a circle
* @param {string} cid is circle identifier
* @param {User} userFollowing is the user for following
* Update user in circle/circles
*/
export let dbAddFollowingUser = (cid: string, userFollowing: UserFollower) => {
export let dbUpdateUserInCircles = (circleIdList: string[], userFollowing: UserTie) => {
return (dispatch: any, getState: Function) => {
const state = getState()
let uid: string = state.authorize.uid
let user: User = { ...state.user.info[uid], userId: uid }
let uid: string = getState().authorize.uid
let user: User = getState().user.info[uid]
let userCircle: User = {
creationDate: moment().unix(),
fullName: userFollowing.fullName,
avatar: userFollowing.avatar || ''
}
let userFollower: UserFollower = {
creationDate: moment().unix(),
fullName: user.fullName,
avatar: user.avatar || '',
approved: false
}
return circleService.addFollowingUser(uid,cid,userCircle,userFollower,userFollowing.userId as string)
return userTieService.tieUseres(
{ userId: user.userId!, fullName: user.fullName, avatar: user.avatar, approved: false },
{ userId: userFollowing.userId!, fullName: userFollowing.fullName, avatar: userFollowing.avatar, approved: false },
circleIdList
)
.then(() => {
dispatch(addFollowingUser(uid, cid, userFollowing.userId as string, { ...userCircle } as User))
dispatch(addFollowingUser(
new UserTie(
userFollowing.userId!,
moment().unix(),
userFollowing.fullName,
userFollowing.avatar,
false,
circleIdList
)))
dispatch(notifyActions.dbAddNotification(
{
@@ -89,18 +92,16 @@ export let dbAddFollowingUser = (cid: string, userFollowing: UserFollower) => {
}
/**
* Delete a user from a circle
* @param {string} cid is circle identifier
* @param {string} userFollowingId following user identifier
* Delete following user
*/
export let dbDeleteFollowingUser = (cid: string, userFollowingId: string) => {
export let dbDeleteFollowingUser = (userFollowingId: string) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
return circleService.deleteFollowingUser(uid,cid,userFollowingId)
return userTieService.removeUsersTie(uid, userFollowingId)
.then(() => {
dispatch(deleteFollowingUser(uid, cid, userFollowingId))
dispatch(deleteFollowingUser(userFollowingId))
}, (error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
@@ -109,7 +110,6 @@ export let dbDeleteFollowingUser = (cid: string, userFollowingId: string) => {
/**
* Update a circle from database
* @param {Circle} newCircle
*/
export const dbUpdateCircle = (newCircle: Circle) => {
return (dispatch: any, getState: Function) => {
@@ -118,14 +118,13 @@ export const dbUpdateCircle = (newCircle: Circle) => {
let uid: string = getState().authorize.uid
// Write the new data simultaneously in the list
let circle: Circle = getState().circle.userCircles[uid][newCircle.id!]
let circle: Circle = getState().circle.userTies[uid][newCircle.id!]
let updatedCircle: Circle = {
name: newCircle.name || circle.name,
users: newCircle.users ? newCircle.users : (circle.users || [])
name: newCircle.name || circle.name
}
return circleService.updateCircle(uid,newCircle.id!,circle)
return circleService.updateCircle(uid, newCircle.id!, circle)
.then(() => {
dispatch(updateCircle(uid,{ id: newCircle.id, ...updatedCircle }))
dispatch(updateCircle({ id: newCircle.id, ...updatedCircle }))
}, (error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
@@ -135,17 +134,16 @@ export const dbUpdateCircle = (newCircle: Circle) => {
/**
* Delete a circle from database
* @param {string} id is circle identifier
*/
export const dbDeleteCircle = (id: string) => {
export const dbDeleteCircle = (circleId: string) => {
return (dispatch: any, getState: Function) => {
// Get current user id
let uid: string = getState().authorize.uid
return circleService.deleteCircle(uid,id)
return circleService.deleteCircle(uid, circleId)
.then(() => {
dispatch(deleteCircle(uid, id))
dispatch(deleteCircle(circleId))
}, (error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
@@ -154,7 +152,7 @@ export const dbDeleteCircle = (id: string) => {
}
/**
* Get all user circles from data base
* Get all circles from data base belong to current user
*/
export const dbGetCircles = () => {
return (dispatch: any, getState: Function) => {
@@ -163,16 +161,7 @@ export const dbGetCircles = () => {
return circleService.getCircles(uid)
.then((circles: { [circleId: string]: Circle }) => {
Object.keys(circles).forEach((circleId) => {
if (circleId !== '-Followers' && circles[circleId].users) {
Object.keys(circles[circleId].users).filter((v, i, a) => a.indexOf(v) === i).forEach((userId) => {
dispatch(postActions.dbGetPostsByUserId(userId))
dispatch(userActions.dbGetUserInfoByUserId(userId, ''))
})
}
})
dispatch(addCircles(uid, circles))
dispatch(addCircles(circles))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
@@ -182,6 +171,46 @@ export const dbGetCircles = () => {
}
}
/**
* Get all user ties from data base
*/
export const dbGetUserTies = () => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
userTieService.getUserTies(uid).then((result) => {
dispatch(userActions.addPeopleInfo(result as any))
dispatch(addUserTies(result))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
}
/**
* Get all followers
*/
export const dbGetFollowers = () => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
userTieService.getUserTies(uid).then((result) => {
dispatch(userActions.addPeopleInfo(result as any))
dispatch(addUserTies(result))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
}
/**
* Get all user circles from data base by user id
* @param uid user identifier
@@ -191,12 +220,12 @@ export const dbGetCirclesByUserId = (uid: string) => {
if (uid) {
return circleService.getCircles(uid)
.then((circles: { [circleId: string]: Circle }) => {
dispatch(addCircles(uid, circles))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
.then((circles: { [circleId: string]: Circle }) => {
dispatch(addCircles(circles))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
}
@@ -204,9 +233,7 @@ export const dbGetCirclesByUserId = (uid: string) => {
/* _____________ CRUD State _____________ */
/**
* Add a normal circle
* @param {string} uid is user identifier
* @param {Circle} circle
* Add a circle
*/
export const addCircle = (circle: Circle) => {
return {
@@ -217,37 +244,31 @@ export const addCircle = (circle: Circle) => {
/**
* Update a circle
* @param {string} uid is user identifier
* @param {Circle} circle
*/
export const updateCircle = (uid: string, circle: Circle) => {
export const updateCircle = (circle: Circle) => {
return {
type: CircleActionType.UPDATE_CIRCLE,
payload: { uid, circle }
payload: { circle }
}
}
/**
* Delete a circle
* @param {string} uid is user identifier
* @param {string} id is circle identifier
*/
export const deleteCircle = (uid: string, id: string) => {
export const deleteCircle = (circleId: string) => {
return {
type: CircleActionType.DELETE_CIRCLE,
payload: { uid, id }
payload: { circleId }
}
}
/**
* Add a list of circle
* @param {string} uid
* @param {circleId: string]: Circle} circles
*/
export const addCircles = (uid: string, circles: { [circleId: string]: Circle }) => {
export const addCircles = (circleList: {[circleId: string]: Circle}) => {
return {
type: CircleActionType.ADD_LIST_CIRCLE,
payload: { uid, circles }
payload: { circleList }
}
}
@@ -262,55 +283,126 @@ export const clearAllCircles = () => {
/**
* Open circle settings
* @param uid user idenifier
* @param id circle identifier
*/
export const openCircleSettings = (uid: string, id: string) => {
export const openCircleSettings = (circleId: string) => {
return {
type: CircleActionType.OPEN_CIRCLE_SETTINGS,
payload: { uid, id }
payload: { circleId }
}
}
/**
* Close open circle settings
* @param uid user identifier
* @param id circle identifier
*/
export const closeCircleSettings = (uid: string, id: string) => {
export const closeCircleSettings = (circleId: string) => {
return {
type: CircleActionType.CLOSE_CIRCLE_SETTINGS,
payload: { uid, id }
payload: { circleId }
}
}
/**
* Add following user in a circle
* @param {string} uid user identifire who want to follow the following user
* @param {string} cid circle identifier that following user should be added in
* @param {string} followingId following user identifier
* @param {User} userCircle information about following user
* Add following user
*/
export const addFollowingUser = (uid: string, cid: string, followingId: string, userCircle: User) => {
export const addFollowingUser = (userTie: UserTie) => {
return {
type: CircleActionType.ADD_FOLLOWING_USER,
payload: { uid, cid, followingId, userCircle }
payload: { userTie }
}
}
/**
* Update the user tie
*/
export const updateUserTie = (userTie: UserTie) => {
return {
type: CircleActionType.UPDATE_USER_TIE,
payload: { userTie }
}
}
/**
* Add user ties
*/
export const addUserTies = (userTies: {[userId: string]: UserTie }) => {
return {
type: CircleActionType.ADD_USER_TIE_LIST,
payload: { userTies }
}
}
/**
* Add users who send tie request for current user
*/
export const addUserTieds = (userTieds: {[userId: string]: UserTie }) => {
return {
type: CircleActionType.ADD_USER_TIED_LIST,
payload: { userTieds }
}
}
/**
* Delete the user from a circle
*/
export const deleteUserFromCircle = (userId: string, circleId: string) => {
return {
type: CircleActionType.DELETE_USER_FROM_CIRCLE,
payload: { userId, circleId }
}
}
/**
* Delete following user
*/
export const deleteFollowingUser = (userId: string) => {
return {
type: CircleActionType.DELETE_FOLLOWING_USER,
payload: { userId }
}
}
/**
* Show the box to select circle
*/
export const showSelectCircleBox = (userId: string) => {
return {
type: CircleActionType.SHOW_SELECT_CIRCLE_BOX,
payload: { userId }
}
}
/**
* Delete following user from a circle
* @param {string} uid user identifire who want to follow the following user
* @param {string} cid circle identifier that following user should be added in
* @param {string} followingId following user identifier
* Hide the box to select circle
*/
export const deleteFollowingUser = (uid: string, cid: string, followingId: string) => {
export const hideSelectCircleBox = (userId: string) => {
return {
type: CircleActionType.DELETE_FOLLOWING_USER,
payload: { uid, cid, followingId }
type: CircleActionType.HIDE_SELECT_CIRCLE_BOX,
payload: { userId }
}
}
/**
* Show loading on following user
*/
export const showFollowingUserLoading = (userId: string) => {
return {
type: CircleActionType.SHOW_FOLLOWING_USER_LOADING,
payload: { userId }
}
}
/**
* Hide loading on following user
*/
export const hideFollowingUserLoading = (userId: string) => {
return {
type: CircleActionType.HIDE_FOLLOWING_USER_LOADING,
payload: { userId }
}
}

View File

@@ -1,5 +1,6 @@
// - Import react components
import moment from 'moment'
import _ from 'lodash'
// - Import domain
import { Comment } from 'core/domain/comments'
@@ -14,11 +15,14 @@ import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import * as postActions from 'actions/postActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICommentService } from 'core/services/comments'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
const serviceProvider: IServiceProvider = new ServiceProvide()
const commentService: ICommentService = serviceProvider.createCommentService()
/**
* Get service providers
*/
const commentService: ICommentService = provider.get<ICommentService>(SocialProviderTypes.CommentService)
/* _____________ CRUD DB _____________ */
@@ -28,11 +32,12 @@ const commentService: ICommentService = serviceProvider.createCommentService()
* @param {object} newComment user comment
* @param {function} callBack will be fired when server responsed
*/
export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, callBack: Function) => {
export const dbAddComment = (ownerPostUserId: string, newComment: Comment, callBack: Function) => {
return (dispatch: any, getState: Function) => {
dispatch(globalActions.showTopLoading())
const state = getState()
let uid: string = getState().authorize.uid
let comment: Comment = {
@@ -74,8 +79,10 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment,
*/
export const dbGetComments = (ownerUserId: string, postId: string) => {
return (dispatch: any, getState: Function) => {
const state = getState()
let uid: string = getState().authorize.uid
if (uid) {
return commentService.getComments(postId, (comments: {[postId: string]: {[commentId: string]: Comment}}) => {
/**
@@ -89,24 +96,21 @@ export const dbGetComments = (ownerUserId: string, postId: string) => {
return
}
if (comments && Object.keys(comments).length > 0) {
commentsCount = Object.keys(comments).length
let sortedObjects = comments as any
const desiredComments = comments[postId]
if (desiredComments && Object.keys(desiredComments).length > 0) {
commentsCount = Object.keys(desiredComments).length
let sortedObjects = desiredComments as any
// Sort posts with creation date
sortedObjects.sort((a: any, b: any) => {
return parseInt(b.creationDate, 10) - parseInt(a.creationDate, 10)
})
if (!post.comments) {
post.comments = {}
}
Object.keys(sortedObjects).slice(0, 3).forEach((commentId) => {
post.comments![commentId] = {
id: commentId,
...sortedObjects[commentId]
}
})
dispatch(postActions.updatePost(post.ownerUserId!,post))
const commentKeys = Object.keys(sortedObjects)
if (commentKeys.length > 1) {
sortedObjects = _.fromPairs(_.toPairs(sortedObjects)
.sort((a: any, b: any) => parseInt(b[1].creationDate,10) - parseInt(a[1].creationDate,10)).slice(0, 3))
}
post.comments = sortedObjects
post.commentCounter = commentsCount
dispatch(postActions.updatePost(post))
}
})
}
@@ -119,29 +123,17 @@ export const dbGetComments = (ownerUserId: string, postId: string) => {
* @param {string} postId is the identifier of the post which comment belong to
* @param {string} text is the text of comment
*/
export const dbUpdateComment = (id: string, postId: string, text: string) => {
export const dbUpdateComment = (comment: Comment) => {
return (dispatch: any, getState: Function) => {
delete comment.editorStatus
dispatch(globalActions.showTopLoading())
// Get current user id
let uid: string = getState().authorize.uid
// Write the new data simultaneously in the list
let comment: Comment = getState().comment.postComments[postId][id]
let updatedComment: Comment = {
postId: postId,
score: comment.score,
text: text,
creationDate: comment.creationDate,
userDisplayName: comment.userDisplayName,
userAvatar: comment.userAvatar,
userId: uid
}
return commentService.updateComment(updatedComment)
return commentService.updateComment(comment)
.then(() => {
dispatch(updateComment( id, postId, text))
dispatch(updateComment( comment))
dispatch(globalActions.hideTopLoading())
}, (error: SocialError) => {
@@ -168,7 +160,7 @@ export const dbDeleteComment = (id?: string | null, postId?: string) => {
// Get current user id
let uid: string = getState().authorize.uid
return commentService.deleteComment(id!,postId!)
return commentService.deleteComment(id!)
.then(() => {
dispatch(deleteComment(id!, postId!))
dispatch(globalActions.hideTopLoading())
@@ -202,11 +194,11 @@ export const addComment = (comment: Comment) => {
* @param postId post identefier which comment belong to
* @param text the new text for comment
*/
export const updateComment = ( id: string, postId: string, text: string) => {
export const updateComment = ( comment: Comment) => {
return {
type: CommentActionType.UPDATE_COMMENT,
payload: {id, postId, text}
payload: { comment }
}
}

View File

@@ -68,6 +68,23 @@ export const hideMessage = () => {
* @param {string} message
*/
export const showErrorMessage = (message: string) => {
const appElement = document.getElementById('app')
const masterElement = document.getElementById('master')
const container = document.createElement('div')
const div = document.createElement('div')
div.innerHTML = message
container.style.position = '100000'
container.style.position = 'fixed'
container.style.backgroundColor = '#32c3e4b8'
container.style.width = '100%'
container.style.height = '100%'
container.style.display = 'flex'
container.style.alignItems = 'center'
container.style.alignItems = 'center'
container.style.flexDirection = 'row'
container.appendChild(div)
appElement!.insertBefore(container, masterElement)
return {
type: GlobalActionType.SHOW_ERROR_MESSAGE_GLOBAL,
payload: message

View File

@@ -15,13 +15,16 @@ import * as globalActions from 'actions/globalActions'
// - Import app API
import FileAPI from 'api/FileAPI'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IImageGalleryService } from 'core/services/imageGallery'
import { FileResult } from 'models/files/fileResult'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
const serviceProvider: IServiceProvider = new ServiceProvide()
const imageGalleryService: IImageGalleryService = serviceProvider.createImageGalleryService()
const storageService: IStorageService = serviceProvider.createStorageService()
/**
* Get service providers
*/
const imageGalleryService: IImageGalleryService = provider.get<IImageGalleryService>(SocialProviderTypes.ImageGalleryService)
const storageService: IStorageService = provider.get<IStorageService>(SocialProviderTypes.StorageService)
/* _____________ UI Actions _____________ */

View File

@@ -12,11 +12,14 @@ import { NotificationActionType } from 'constants/notificationActionType'
import * as globalActions from 'actions/globalActions'
import * as userActions from 'actions/userActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { INotificationService } from 'core/services/notifications'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
const serviceProvider: IServiceProvider = new ServiceProvide()
const notificationService: INotificationService = serviceProvider.createNotificationService()
/**
* Get service providers
*/
const notificationService: INotificationService = provider.get<INotificationService>(SocialProviderTypes.NotificationService)
/* _____________ CRUD DB _____________ */

View File

@@ -15,11 +15,14 @@ import { PostActionType } from 'constants/postActionType'
// - Import actions
import * as globalActions from 'actions/globalActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IPostService } from 'core/services/posts'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
const serviceProvider: IServiceProvider = new ServiceProvide()
const postService: IPostService = serviceProvider.createPostService()
/**
* Get service providers
*/
const postService: IPostService = provider.get<IPostService>(SocialProviderTypes.PostService)
/* _____________ CRUD DB _____________ */
@@ -127,7 +130,7 @@ export const dbUpdatePost = (updatedPost: Post, callBack: Function) => {
return postService.updatePost(updatedPost).then(() => {
dispatch(updatePost(uid, { ...updatedPost }))
dispatch(updatePost(updatedPost))
callBack()
dispatch(globalActions.hideTopLoading())
@@ -169,13 +172,88 @@ export const dbDeletePost = (id: string) => {
/**
* Get all user posts from data base
*/
export const dbGetPosts = () => {
export const dbGetPosts = (page: number = 0, limit: number = 10) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
const state = getState()
const {stream} = state.post
const lastPageRequest = stream.lastPageRequest
const lastPostId = stream.lastPostId
const hasMoreData = stream.hasMoreData
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {
dispatch(addPosts(uid, posts))
let uid: string = state.authorize.uid
if (uid && lastPageRequest !== page) {
return postService.getPosts(uid, lastPostId, page, limit).then((result) => {
if (!result.posts || !(result.posts.length > 0)) {
return dispatch(notMoreDataStream())
}
// Store last post Id
dispatch(lastPostStream(result.newLastPostId))
let parsedData: { [userId: string]: {[postId: string]: Post} } = {}
result.posts.forEach((post) => {
const postId = Object.keys(post)[0]
const postData = post[postId]
const ownerId = postData.ownerUserId!
parsedData = {
...parsedData,
[ownerId]: {
...parsedData[ownerId],
[postId]: {
...postData
}
}
}
})
dispatch(addPosts(parsedData))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
}
/**
* Get all user posts from data base
*/
export const dbGetPostsByUserId = (page: number = 0, limit: number = 10) => {
return (dispatch: any, getState: Function) => {
const state = getState()
const {profile} = state.post
const lastPageRequest = profile.lastPageRequest
const lastPostId = profile.lastPostId
const hasMoreData = profile.hasMoreData
let uid: string = state.authorize.uid
if (uid && lastPageRequest !== page) {
return postService.getPostsByUserId(uid, lastPostId, page, limit).then((result) => {
if (!result.posts || !(result.posts.length > 0)) {
return dispatch(notMoreDataProfile())
}
// Store last post Id
dispatch(lastPostProfile(result.newLastPostId))
let parsedData: { [userId: string]: {[postId: string]: Post} } = {}
result.posts.forEach((post) => {
const postId = Object.keys(post)[0]
const postData = post[postId]
const ownerId = postData.ownerUserId!
parsedData = {
...parsedData,
[ownerId]: {
...parsedData[ownerId],
[postId]: {
...postData
}
}
}
})
dispatch(addPosts(parsedData))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
@@ -205,22 +283,6 @@ export const dbGetPostById = (uid: string, postId: string) => {
}
}
/**
* Get all user posts from data base by user id
* @param uid posts owner identifier
*/
export const dbGetPostsByUserId = (uid: string) => {
return (dispatch: Function, getState: Function) => {
if (uid) {
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {
dispatch(addPosts(uid, posts))
})
}
}
}
/* _____________ CRUD State _____________ */
/**
@@ -240,10 +302,10 @@ export const addPost = (uid: string, post: Post) => {
* @param {string} uid is user identifier
* @param {Post} post
*/
export const updatePost = (uid: string, post: Post) => {
export const updatePost = (post: Post) => {
return {
type: PostActionType.UPDATE_POST,
payload: { uid, post }
payload: { post }
}
}
@@ -264,10 +326,10 @@ export const deletePost = (uid: string, id: string) => {
* @param {string} uid
* @param {[object]} posts
*/
export const addPosts = (uid: string, posts: { [postId: string]: Post }) => {
export const addPosts = (userPosts: { [userId: string]: {[postId: string]: Post} }) => {
return {
type: PostActionType.ADD_LIST_POST,
payload: { uid, posts }
payload: { userPosts }
}
}
@@ -282,7 +344,6 @@ export const clearAllData = () => {
/**
* Add a post with image
* @param {object} post
*/
export const addImagePost = (uid: string, post: any) => {
return {
@@ -291,3 +352,88 @@ export const addImagePost = (uid: string, post: any) => {
}
}
/**
* Set stream has more data to show
*/
export const hasMoreDataStream = () => {
return {
type: PostActionType.HAS_MORE_DATA_STREAM
}
}
/**
* Set stream has not data any more to show
*/
export const notMoreDataStream = () => {
return {
type: PostActionType.NOT_MORE_DATA_STREAM
}
}
/**
* Set last page request of stream
*/
export const requestPageStream = (page: number) => {
return {
type: PostActionType.REQUEST_PAGE_STREAM,
payload: { page}
}
}
/**
* Set last post identification of stream
*/
export const lastPostStream = (lastPostId: string) => {
return {
type: PostActionType.LAST_POST_STREAM,
payload: { lastPostId}
}
}
/**
* Set profile posts has more data to show
*/
export const hasMoreDataProfile = () => {
return {
type: PostActionType.HAS_MORE_DATA_PROFILE
}
}
/**
* Set profile posts has not data any more to show
*/
export const notMoreDataProfile = () => {
return {
type: PostActionType.NOT_MORE_DATA_PROFILE
}
}
/**
* Set last page request of profile posts
*/
export const requestPageProfile = (page: number) => {
return {
type: PostActionType.REQUEST_PAGE_PROFILE,
payload: { page}
}
}
/**
* Set last post identification of profile posts
*/
export const lastPostProfile = (lastPostId: string) => {
return {
type: PostActionType.LAST_POST_PROFILE,
payload: { lastPostId}
}
}

View File

@@ -0,0 +1,51 @@
import moment from 'moment'
// - Import action types
import { ServerActionType } from 'constants/serverActionType'
// - Import domain
// - Import actions
import * as globalActions from 'actions/globalActions'
import { ServerRequestModel } from 'models/server/serverRequestModel'
import { SocialError } from 'core/domain/common/socialError'
/**
* Add a request
* @param {Request} request
*/
export const sendRequest = (request: ServerRequestModel) => {
return { type: ServerActionType.ADD_REQUEST, payload: request }
}
/**
* delete a request
*/
export const deleteRequest = (requestId: string) => {
return { type: ServerActionType.DELETE_REQUEST, payload: {requestId} }
}
/**
* Update request stattus ti successful
*/
export const okRequest = (requestId: string) => {
return { type: ServerActionType.OK_REQUEST, payload: {requestId} }
}
/**
* Set error request
*/
export const errorRequest = (requestId: string, error: SocialError) => {
return { type: ServerActionType.ERROR_REQUEST, payload: {requestId, error} }
}
/**
* Clear all data
*/
export const clearAllrequests = () => {
return { type: ServerActionType.CLEAR_ALL_DATA_REQUEST }
}

View File

@@ -0,0 +1,5 @@
export enum ServerRequestStatusType {
Sent = 'Sent',
OK = 'OK',
Error = 'Error'
}

View File

@@ -1,4 +1,5 @@
// - Import react components
import { provider } from '../socialEngine'
import _ from 'lodash'
// - Import domain
import { Profile } from 'core/domain/users'
@@ -11,11 +12,13 @@ import { UserActionType } from 'constants/userActionType'
import * as globalActions from 'actions/globalActions'
import * as userActions from 'actions/userActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IUserService } from 'core/services/users'
import { SocialProviderTypes } from 'core/socialProviderTypes'
const serviceProvider: IServiceProvider = new ServiceProvide()
const userService: IUserService = serviceProvider.createUserService()
/**
* Get service providers
*/
const userService: IUserService = provider.get<IUserService>(SocialProviderTypes.UserService)
/* _____________ CRUD DB _____________ */
@@ -93,7 +96,7 @@ export const dbUpdateUserInfo = (newProfile: Profile) => {
let uid: string = getState().authorize.uid
let profile: Profile = getState().user.info[uid]
let updatedProfie: Profile = {
let updatedProfile: Profile = {
avatar: newProfile.avatar || profile.avatar || '',
banner: newProfile.banner || profile.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
email: newProfile.email || profile.email || '',
@@ -101,10 +104,9 @@ export const dbUpdateUserInfo = (newProfile: Profile) => {
tagLine: newProfile.tagLine || profile.tagLine || '',
creationDate: newProfile.creationDate
}
return userService.updateUserProfile(uid,updatedProfile).then(() => {
return userService.updateUserProfile(uid,updatedProfie).then(() => {
dispatch(updateUserInfo(uid, updatedProfie))
dispatch(updateUserInfo(uid, updatedProfile))
dispatch(closeEditProfile())
})
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
@@ -114,17 +116,40 @@ export const dbUpdateUserInfo = (newProfile: Profile) => {
}
// - Get people info from database
export const dbGetPeopleInfo = (page?: number) => {
export const dbGetPeopleInfo = (page: number, limit: number) => {
return (dispatch: any, getState: Function) => {
const {authorize, user} = getState()
let uid: string = authorize.uid
if (uid) {
const lastKey = ''
return userService.getUsersProfile(uid, lastKey)
.then((usersProfile: {[userId: string]: Profile}) => {
dispatch(addPeopleInfo(usersProfile))
const state = getState()
const {people} = state.user
const lastPageRequest = people.lastPageRequest
const lastUserId = people.lastUserId
const hasMoreData = people.hasMoreData
let uid: string = state.authorize.uid
if (uid && lastPageRequest !== page) {
return userService.getUsersProfile(uid, lastUserId, page, limit).then((result) => {
if (!result.users || !(result.users.length > 0)) {
return dispatch(notMoreDataPeople())
}
// Store last user Id
dispatch(lastUserPeople(result.newLastUserId))
let parsedData: {[userId: string]: Profile} = {}
result.users.forEach((post) => {
const userId = Object.keys(post)[0]
const postData = post[userId]
parsedData = {
...parsedData,
[userId]: {
...postData
}
}
})
dispatch(addPeopleInfo(parsedData))
})
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
}
}
@@ -167,17 +192,6 @@ export const updateUserInfo = (uid: string, info: Profile) => {
}
}
/**
* User info
* @param {Profile} info
*/
export const userInfo = (info: Profile) => {
return {
type: UserActionType.USER_INFO,
info
}
}
export const clearAllData = () => {
return {
type: UserActionType.CLEAR_ALL_DATA_USER
@@ -203,3 +217,45 @@ export const closeEditProfile = () => {
}
}
/**
* Set profile posts has more data to show
*/
export const hasMoreDataPeople = () => {
return {
type: UserActionType.HAS_MORE_DATA_PEOPLE
}
}
/**
* Set profile posts has not data any more to show
*/
export const notMoreDataPeople = () => {
return {
type: UserActionType.NOT_MORE_DATA_PEOPLE
}
}
/**
* Set last page request of profile posts
*/
export const requestPagePeople = (page: number) => {
return {
type: UserActionType.REQUEST_PAGE_PEOPLE,
payload: { page}
}
}
/**
* Set last user identification of find people page
*/
export const lastUserPeople = (lastUserId: string) => {
return {
type: UserActionType.LAST_USER_PEOPLE,
payload: { lastUserId}
}
}

View File

@@ -11,12 +11,15 @@ import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import * as postActions from 'actions/postActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IVoteService } from 'core/services/votes'
import { Post } from 'core/domain/posts'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { provider } from '../socialEngine'
const serviceProvider: IServiceProvider = new ServiceProvide()
const voteService: IVoteService = serviceProvider.createVoteService()
/**
* Get service providers
*/
const voteService: IVoteService = provider.get<IVoteService>(SocialProviderTypes.VoteService)
/* _____________ CRUD DB _____________ */
@@ -40,7 +43,8 @@ export const dbAddVote = (postId: string,ownerPostUserId: string) => {
const post: Post = state.post.userPosts[ownerPostUserId][postId]
post.score! += 1
dispatch(postActions.updatePost(ownerPostUserId,post))
post.votes = { ...post.votes!, [uid]: true}
dispatch(postActions.updatePost(post))
return voteService.addVote(vote).then((voteKey: string) => {
if (uid !== ownerPostUserId) {
@@ -56,7 +60,8 @@ export const dbAddVote = (postId: string,ownerPostUserId: string) => {
})
.catch((error) => {
post.score! -= 1
dispatch(postActions.updatePost(ownerPostUserId,post))
post.votes = { ...post.votes!, [uid]: false}
dispatch(postActions.updatePost(post))
dispatch(globalActions.showErrorMessage(error.message))
})
}
@@ -99,17 +104,15 @@ export const dbDeleteVote = (postId: string, ownerPostUserId: string) => {
const state = getState()
// Get current user id
let uid: string = state.authorize.uid
let votes: {[voteId: string]: Vote} = getState().vote.postVotes[postId]
let id: string = Object.keys(votes).filter((key) => votes[key].userId === uid)[0]
const vote = votes[id]
const post: Post = state.post.userPosts[ownerPostUserId][postId]
post.score! -= 1
dispatch(postActions.updatePost(ownerPostUserId,post))
return voteService.deleteVote(vote).then(x => x)
post.votes = { ...post.votes!, [uid]: false}
dispatch(postActions.updatePost(post))
return voteService.deleteVote(uid, postId).then(x => x)
.catch((error: any) => {
post.score! += 1
dispatch(postActions.updatePost(ownerPostUserId,post))
post.votes = { ...post.votes!, [uid]: true}
dispatch(postActions.updatePost(post))
dispatch(globalActions.showErrorMessage(error.message))
})
}
@@ -129,8 +132,8 @@ export const addVote = (vote: Vote) => {
* @param {string} id vote identifier
* @param {string} postId post identifier which vote on
*/
export const deleteVote = (id: string, postId: string) => {
return { type: VoteActionType.DELETE_VOTE, payload: {id, postId} }
export const deleteVote = (userId: string, postId: string) => {
return { type: VoteActionType.DELETE_VOTE, payload: {userId, postId} }
}

View File

@@ -1,4 +1,4 @@
import { Circle, UserFollower } from 'core/domain/circles'
import { Circle, UserTie } from 'core/domain/circles'
/**
* Get the circles' id which the specify users is in that circle
@@ -24,7 +24,7 @@ export const getUserBelongCircles = (circles: {[circleId: string]: Circle},follo
* @param {object} circles
*/
export const getFollowingUsers = (circles: {[circleId: string]: Circle}) => {
let followingUsers: {[userId: string]: UserFollower} = {}
let followingUsers: {[userId: string]: UserTie} = {}
Object.keys(circles).forEach((cid) => {
if (cid.trim() !== '-Followers' && circles[cid].users) {
Object.keys(circles[cid].users).forEach((userId) => {

32
src/api/CommonAPI.ts Normal file
View File

@@ -0,0 +1,32 @@
/**
* Log the data
* @param title log title
* @param data log data object
*/
const logger = (title: string, data: any, trace?: boolean) => {
const randomColor = getRandomColor()
if (trace) {
console.trace()
}
console.log(`\n\n\n%c ${title} :\n`, `color:${getRandomColor()};font-size:15`)
console.log('%c =========================================', `color:${randomColor}`)
console.log(data)
console.log('%c =========================================', `color:${randomColor}`)
}
/**
* Get random color in hex
*/
const getRandomColor = () => {
let letters = '0123456789ABCDEF'
let color = '#'
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)]
}
return color
}
export default {
logger,
getRandomColor
}

View File

@@ -1,3 +1,5 @@
import { ServerRequestType } from 'constants/serverRequestType'
// - Import actions
const isValidEmail = (email: string) => {
@@ -5,6 +7,10 @@ const isValidEmail = (email: string) => {
return re.test(email)
}
const createServerRequestId = (requestType: ServerRequestType, uniqueId: string) => {
return `${requestType}:${uniqueId}`
}
function queryString (name: string, url: string = window.location.href) {
name = name.replace(/[[]]/g, '\\$&')
@@ -23,5 +29,6 @@ function queryString (name: string, url: string = window.location.href) {
export default {
isValidEmail,
queryString
queryString,
createServerRequestId
}

View File

@@ -244,8 +244,8 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
return {
deleteCircle: (id: string) => dispatch(circleActions.dbDeleteCircle(id)),
updateCircle: (circle: Circle) => dispatch(circleActions.dbUpdateCircle(circle)),
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid, ownProps.id)),
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid, ownProps.id)),
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(ownProps.id)),
openCircleSettings: () => dispatch(circleActions.openCircleSettings(ownProps.id)),
goTo: (url: string) => dispatch(push(url))
}
@@ -258,9 +258,12 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: ICircleComponentProps) => {
let { uid } = state.authorize
const {circle, authorize, server} = state
const { uid } = state.authorize
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
const currentCircle = (circles ? circles[ownProps.id] : {}) as Circle
return {
openSetting: state.circle ? (state.circle.userCircles[uid] ? (state.circle.userCircles[uid][ownProps.id].openCircleSettings || false) : false) : false,
openSetting: state.circle ? (currentCircle ? (currentCircle.openCircleSettings || false) : false) : false,
userInfo: state.user.info
}

View File

@@ -6,6 +6,8 @@ import PropTypes from 'prop-types'
import moment from 'moment'
import Linkify from 'react-linkify'
import { Comment } from 'core/domain/comments'
// - Import material UI libraries
import { List, ListItem } from 'material-ui/List'
import Divider from 'material-ui/Divider'
@@ -183,8 +185,10 @@ export class CommentComponent extends Component<ICommentComponentProps,ICommentC
* @param {event} evt is an event passed by clicking on post button
*/
handleUpdateComment = (evt: any) => {
this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text)
const {comment} = this.props
comment.editorStatus = undefined
comment.text = this.state.text
this.props.update(comment)
this.setState({
initialText: this.state.text
})
@@ -302,13 +306,18 @@ export class CommentComponent extends Component<ICommentComponentProps,ICommentC
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any, ownProps: any) => {
const mapDispatchToProps = (dispatch: any, ownProps: ICommentComponentProps) => {
return {
delete: (id: string| null, postId: string) => dispatch(commentActions.dbDeleteComment(id, postId)),
update: (id: string, postId: string, comment: string) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
update: (comment: Comment) => {
console.log('====================================')
console.log(comment)
console.log('====================================')
dispatch(commentActions.dbUpdateComment(comment))
},
openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
closeEditor: () => dispatch(commentActions.closeCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId,''))
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId!,''))
}
}

View File

@@ -47,7 +47,7 @@ export interface ICommentComponentProps {
*
* @memberof ICommentComponentProps
*/
update: (id?: string | null, postId?: string, comment?: string | null) => any
update: (comment: Comment) => any
/**
* Delete comment

View File

@@ -1,6 +1,7 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import { connect } from 'react-redux'
import Paper from 'material-ui/Paper'
import FlatButton from 'material-ui/FlatButton'
@@ -101,7 +102,7 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
clearCommentWrite = () => {
this.setState({
commentText: '',
postDisable: false
postDisable: true
})
}
@@ -109,7 +110,10 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
* Post comment
*/
handlePostComment = () => {
this.props.send!(this.state.commentText, this.props.postId, this.clearCommentWrite)
this.clearCommentWrite()
}
/**
@@ -140,9 +144,10 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
commentList = () => {
let comments = this.props.commentSlides
if (comments) {
comments = _.fromPairs(_.toPairs(comments)
.sort((a: any, b: any) => parseInt(b[1].creationDate,10) - parseInt(a[1].creationDate,10)))
let parsedComments: Comment[] = []
Object.keys(comments).slice(0, 3).forEach((commentId) => {
Object.keys(comments).forEach((commentId) => {
parsedComments.push({
id: commentId,
...comments![commentId]
@@ -222,6 +227,7 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
<FlatButton primary={true} disabled={this.state.postDisable} label='Post' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handlePostComment} />
</Paper>
</div>)
/**
* Return Elements
*/

View File

@@ -227,7 +227,8 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,I
fullName: fullNameInput,
tagLine: tagLineInput,
avatar: avatar,
banner: banner
banner: banner,
creationDate: this.props.info!.creationDate
})
}
}

View File

@@ -7,6 +7,7 @@ import InfiniteScroll from 'react-infinite-scroller'
// - Import app components
import UserBoxList from 'components/userBoxList'
import LoadMoreProgressComponent from 'layouts/loadMoreProgress'
// - Import API
@@ -20,10 +21,6 @@ import { IFindPeopleComponentState } from './IFindPeopleComponentState'
*/
export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IFindPeopleComponentState> {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
@@ -36,49 +33,32 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
}
// Binding functions to `this`
}
loadItems (page: number) {
console.log('------------------------')
console.log(page)
console.log('------------------------')
/**
* Scroll loader
*/
scrollLoad = (page: number) => {
const {loadPeople} = this.props
loadPeople!(page, 10)
}
componentWillMount () {
this.props.loadPeople!()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render () {
const styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton: {
position: 'absolute',
bottom: '8px',
left: 0,
right: 0
}
}
const loader = <div className='loader'>Loading ...</div>
const {hasMorePeople} = this.props
return (
<div>
<InfiniteScroll
pageStart={0}
loadMore={this.loadItems.bind(this)}
hasMore={false}
loader={loader}>
loadMore={this.scrollLoad}
hasMore={hasMorePeople}
useWindow={true}
loader={ <LoadMoreProgressComponent />}
>
<div className='tracks'>
@@ -106,7 +86,7 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
*/
const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps) => {
return {
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
loadPeople: (page: number, limit: number) => dispatch(userActions.dbGetPeopleInfo(page, limit))
}
}
@@ -117,8 +97,10 @@ const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps)
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => {
const {people, info} = state.user
return {
peopleInfo: state.user.info
peopleInfo: info,
hasMorePeople: people.hasMoreData
}
}

View File

@@ -7,7 +7,7 @@ export interface IFindPeopleComponentProps {
*
* @memberof IFindPeopleComponentProps
*/
loadPeople?: () => any
loadPeople?: (page: number, limit: number) => any
/**
* Users' profile
@@ -17,4 +17,9 @@ export interface IFindPeopleComponentProps {
*/
peopleInfo?: {[userId: string]: Profile}
/**
* If there are more people {true} or not {false}
*/
hasMorePeople: boolean
}

View File

@@ -8,6 +8,7 @@ import UserBoxList from 'components/userBoxList'
import { IFollowersComponentProps } from './IFollowersComponentProps'
import { IFollowersComponentState } from './IFollowersComponentState'
import { Circle } from 'core/domain/circles';
// - Import API
@@ -79,10 +80,11 @@ const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) =>
* @return {object} props of component
*/
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
const {circle, authorize, server} = state
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
return{
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
followers: circles ? circles.userTieds : {}
}
}

View File

@@ -1,12 +1,12 @@
import { UserFollower } from 'core/domain/circles'
import { UserTie } from 'core/domain/circles'
export interface IFollowersComponentProps {
/**
* User followers info
*
* @type {{[userId: string]: UserFollower}}
* @type {{[userId: string]: UserTie}}
* @memberof IFindPeopleComponentProps
*/
followers?: {[userId: string]: UserFollower}
followers?: {[userId: string]: UserTie}
}

View File

@@ -10,6 +10,7 @@ import UserBoxList from 'components/userBoxList'
import CircleAPI from 'api/CircleAPI'
import { IFollowingComponentProps } from './IFollowingComponentProps'
import { IFollowingComponentState } from './IFollowingComponentState'
import { Circle } from 'core/domain/circles';
// - Import actions
@@ -80,9 +81,10 @@ const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) =>
* @return {object} props of component
*/
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
const {circle, authorize, server} = state
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
const followingUsers = circle ? circle.userTies : {}
return {
uid,
circles,

View File

@@ -1,6 +1,6 @@
import { UserFollower } from 'core/domain/circles'
import { UserTie } from 'core/domain/circles'
export interface IFollowingComponentProps {
followingUsers?: {[userId: string]: UserFollower}
followingUsers?: {[userId: string]: UserTie}
}

View File

@@ -106,7 +106,6 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
}
componentWillMount () {
const {global, clearData, loadData, authed, defaultDataEnable, isVerifide, goTo } = this.props
if (!authed) {
goTo!('/login')
@@ -131,7 +130,7 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
* @memberof Home
*/
render () {
const {loaded, authed, mergedPosts} = this.props
const {loaded, authed, loadDataStream, mergedPosts, hasMorePosts} = this.props
return (
<div id='home'>
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
@@ -153,7 +152,7 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
</SidebarContent>
<SidebarMain>
<HomeRouter enabled={loaded!} data={{mergedPosts}} />
<HomeRouter enabled={loaded!} data={{ mergedPosts, loadDataStream, hasMorePosts}} />
</SidebarMain>
</Sidebar>
@@ -167,12 +166,15 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return {
loadDataStream:
(page: number, limit: number) => dispatch(postActions.dbGetPosts(page,limit)),
loadData: () => {
dispatch(imageGalleryActions.dbGetImageGallery())
dispatch(postActions.dbGetPosts())
dispatch(imageGalleryActions.dbGetImageGallery())
dispatch(userActions.dbGetUserInfo())
dispatch(notifyActions.dbGetNotifications())
dispatch(circleActions.dbGetCircles())
dispatch(circleActions.dbGetUserTies())
},
clearData: () => {
@@ -205,9 +207,10 @@ const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
const { authorize, global, user, post, imageGallery, notify, circle } = state
const { uid } = authorize
let mergedPosts = {}
const circles = circle ? (circle.userCircles[uid] || {}) : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
const circles = circle ? (circle.circleList || {}) : {}
const followingUsers = circle ? circle.userTies : {}
const posts = post.userPosts ? post.userPosts[authorize.uid] : {}
const hasMorePosts = post.stream.hasMoreData
Object.keys(followingUsers).forEach((userId) => {
let newPosts = post.userPosts ? post.userPosts[userId] : {}
_.merge(mergedPosts,newPosts)
@@ -219,9 +222,10 @@ const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
mainStyle: global.sidebarMainStyle,
mergedPosts,
global,
hasMorePosts,
loaded: user.loaded && post.loaded && imageGallery.loaded && notify.loaded && circle.loaded
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any))
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any) as any)

View File

@@ -24,7 +24,7 @@ export interface IHomeComponentProps {
* @type {string}
* @memberof IHomeComponentProps
*/
uid: string
uid?: string
/**
* Merged all users posts to show in stream
@@ -34,6 +34,11 @@ export interface IHomeComponentProps {
*/
mergedPosts?: {[postId: string]: Post}
/**
* Load the data for stream
*/
loadDataStream: (lastPostId: string, page: number, limit: number) => any
/**
* Global state
*
@@ -80,6 +85,11 @@ export interface IHomeComponentProps {
*/
goTo?: (url: string) => any
/**
* If there is more post {true} or not {false}
*/
hasMorePosts?: boolean
/**
* If all requierment data loaded {true} or not {false}
*

View File

@@ -29,7 +29,6 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps,IHo
styles = {
toolbarStyle: {
backgroundColor: '',
transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
boxSizing: 'border-box',
fontFamily: 'Roboto, sans-serif',
@@ -187,7 +186,7 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps,IHo
return (
<Toolbar style={this.styles.toolbarStyle as any} className='g__greenBox'>
<Toolbar style={this.styles.toolbarStyle as any}>
<EventListener
target='window'
onResize={this.handleResize}

View File

@@ -191,4 +191,4 @@ const mapStateToProps = (state: any) => {
}
// - Connect commponent to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MasterComponent as any))
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MasterComponent as any) as any)

View File

@@ -17,7 +17,7 @@ export interface IPostComponentProps {
* @type {string}
* @memberof IPostComponentProps
*/
avatar: string
avatar?: string
/**
* User full name
@@ -105,7 +105,7 @@ export interface IPostComponentProps {
*
* @memberof IPostComponentProps
*/
getPostComments: (ownerUserId: string, postId: string) => any
getPostComments?: (ownerUserId: string, postId: string) => any
/**
* Commnets

View File

@@ -87,7 +87,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
/**
* Post text
*/
text: post.body!,
text: post.body ? post.body : '',
/**
* It's true if whole the text post is visible
*/
@@ -409,7 +409,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
const mapStateToProps = (state: any, ownProps: IPostComponentProps) => {
const {post, vote, authorize, comment} = state
const {uid} = authorize
let currentUserVote = post.votes ? post.votes[uid] : false
let currentUserVote = ownProps.post.votes ? ownProps.post.votes[uid] : false
const postModel = post.userPosts[ownProps.post.ownerUserId!][ownProps.post.id!]
const postOwner = (post.userPosts[uid] ? Object.keys(post.userPosts[uid]).filter((key) => { return ownProps.post.id === key }).length : 0)
const commentList: { [commentId: string]: Comment } = comment.postComments[ownProps.post.id!]

View File

@@ -50,12 +50,12 @@ export interface IPostWriteComponentProps {
/**
* The post owner name
*/
ownerDisplayName: string
ownerDisplayName?: string
/**
* Post model
*/
postModel: Post
postModel?: Post
/**
* Save a post

View File

@@ -49,15 +49,15 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
/**
* Post text
*/
postText: this.props.edit ? this.props.text! : '',
postText: this.props.edit && postModel ? (postModel.body ? postModel.body! : '' ) : '',
/**
* The URL image of the post
*/
image: this.props.edit ? this.props.image : '',
image: this.props.edit && postModel ? (postModel.image ? postModel.image! : '' ) : '',
/**
* The path identifier of image on the server
*/
imageFullPath: this.props.edit ? (postModel.imageFullPath ? postModel.imageFullPath! : '' ) : '',
imageFullPath: this.props.edit && postModel ? (postModel.imageFullPath ? postModel.imageFullPath! : '' ) : '',
/**
* If it's true gallery will be open
*/
@@ -69,11 +69,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? postModel.disableComments! : false,
disableComments: this.props.edit && postModel ? postModel.disableComments! : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? postModel.disableSharing! : false
disableSharing: this.props.edit && postModel ? postModel.disableSharing! : false
}
@@ -190,14 +190,14 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
}, onRequestClose)
}
} else { // In edit status we pass post to update functions
postModel.body = postText
postModel.tags = tags
postModel.image = image
postModel.imageFullPath = imageFullPath
postModel.disableComments = disableComments
postModel.disableSharing = disableSharing
postModel!.body = postText
postModel!.tags = tags
postModel!.image = image
postModel!.imageFullPath = imageFullPath
postModel!.disableComments = disableComments
postModel!.disableSharing = disableSharing
update!(postModel, onRequestClose)
update!(postModel!, onRequestClose)
}
}
@@ -255,30 +255,34 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
if (!nextProps.open) {
const {postModel} = this.props
this.setState({
/**
* Post text
*/
postText: this.props.edit ? this.props.text! : '',
/**
* The image of the post
*/
image: this.props.edit ? this.props.image! : '',
/**
* If it's true gallery will be open
*/
/**
* Post text
*/
postText: this.props.edit && postModel ? (postModel.body ? postModel.body! : '' ) : '',
/**
* The URL image of the post
*/
image: this.props.edit && postModel ? (postModel.image ? postModel.image! : '' ) : '',
/**
* The path identifier of image on the server
*/
imageFullPath: this.props.edit && postModel ? (postModel.imageFullPath ? postModel.imageFullPath! : '' ) : '',
/**
* If it's true gallery will be open
*/
galleryOpen: false,
/**
* If it's true post button will be disabled
*/
/**
* If it's true post button will be disabled
*/
disabledPost: true,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? postModel.disableComments! : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? postModel.disableSharing! : false
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit && postModel ? postModel.disableComments! : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit && postModel ? postModel.disableSharing! : false
})
}

View File

@@ -79,4 +79,9 @@ export interface IProfileComponentProps {
* @memberof IProfileComponentProps
*/
loadUserInfo: () => any
/**
* If there is more posts to show in profile
*/
hasMorePosts: boolean
}

View File

@@ -77,6 +77,7 @@ export class ProfileComponent extends Component<IProfileComponentProps,IProfileC
border: '2px solid rgb(255, 255, 255)'
}
}
const {loadPosts, hasMorePosts} = this.props
return (
<div style={styles.profile}>
@@ -91,7 +92,11 @@ export class ProfileComponent extends Component<IProfileComponentProps,IProfileC
</div>
<div style={{ height: '24px' }}></div>
<StreamComponent posts={this.props.posts} displayWriting={false} />
<StreamComponent
posts={this.props.posts}
loadStream={loadPosts}
hasMorePosts={hasMorePosts}
displayWriting={false} />
</div>)
: (<div className='profile__title'>
Nothing shared
@@ -127,6 +132,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IProfileComponentProps) =>
const mapStateToProps = (state: any, ownProps: IProfileComponentProps) => {
const { userId } = ownProps.match.params
const {uid} = state.authorize
const hasMorePosts = state.post.profile.hasMoreData
return {
avatar: state.user.info && state.user.info[userId] ? state.user.info[userId].avatar || '' : '',
name: state.user.info && state.user.info[userId] ? state.user.info[userId].fullName || '' : '',
@@ -134,7 +140,8 @@ const mapStateToProps = (state: any, ownProps: IProfileComponentProps) => {
tagLine: state.user.info && state.user.info[userId] ? state.user.info[userId].tagLine || '' : '',
posts: state.post.userPosts ? state.post.userPosts[userId] : {},
isAuthedUser: userId === uid,
userId
userId,
hasMorePosts
}
}

View File

@@ -0,0 +1,92 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { grey400 } from 'material-ui/styles/colors'
import SvgClose from 'material-ui/svg-icons/navigation/close'
import FlatButton from 'material-ui/FlatButton'
import Divider from 'material-ui/Divider'
import { IDialogTitleComponentProps } from './IDialogTitleComponentProps'
import { IDialogTitleComponentState } from './IDialogTitleComponentState'
/**
* Create component class
*/
export default class DialogTitleComponent extends Component<IDialogTitleComponentProps,IDialogTitleComponentState> {
static propTypes = {
/**
* The label of right button
*/
buttonLabel: PropTypes.string,
/**
* If it's true button will be disabled
*/
disabledButton: PropTypes.bool,
/**
* Call the funtion the time is clicked on right button
*/
onClickButton: PropTypes.func,
/**
* The function will be called the time is clicked on close
*/
onRequestClose: PropTypes.func.isRequired,
/**
* The title of dialog box
*/
title: PropTypes.string
}
styles = {
contain: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between'
},
title: {
color: 'rgba(0,0,0,0.87)',
flex: '1 1',
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: IDialogTitleComponentProps) {
super(props)
// Defaul state
this.state = {
}
// Binding functions to `this`
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render () {
const { buttonLabel, disabledButton, onClickButton, onRequestClose, title } = this.props
return (
<div className='g__dialog-title'>
<div style={this.styles.contain as any}>
<div style={{ paddingRight: '10px' }}>
<SvgClose onClick={onRequestClose} hoverColor={grey400} style={{ cursor: 'pointer' }} />
</div>
<div style={this.styles.title}>
{title || ''}
</div>
{ buttonLabel ? (<div style={{ marginTop: '-9px' }}>
<FlatButton label={buttonLabel || ''} primary={true} disabled={disabledButton ? disabledButton : false} onClick={onClickButton || (x => x)} />
</div>) : ''}
</div>
<Divider />
</div>
)
}
}

View File

@@ -0,0 +1,40 @@
export interface IDialogTitleComponentProps {
/**
* Lable of the button
*
* @type {string}
* @memberof IDialogTitleComponentProps
*/
buttonLabel?: string
/**
* Dialog tile
*
* @type {string}
* @memberof IDialogTitleComponentProps
*/
title: string
/**
* Button is disabled {true} or not {false}
*
* @type {boolean}
* @memberof IDialogTitleComponentProps
*/
disabledButton?: boolean
/**
* On click event
*
* @memberof IDialogTitleComponentProps
*/
onClickButton?: (event: any) => void
/**
* On request close event
*
* @memberof IDialogTitleComponentProps
*/
onRequestClose: (event: any) => void
}

View File

@@ -0,0 +1,3 @@
export interface IDialogTitleComponentState {
}

View File

@@ -0,0 +1,2 @@
import DialogTitleComponent from './DialogTitleComponent'
export default DialogTitleComponent

View File

@@ -66,12 +66,26 @@ export interface IStreamComponentProps {
*/
avatar?: string
/**
* Load the data for stream
*/
loadStream: (page: number, limit: number) => any
/**
* If there is more post {true} or not {false}
*/
hasMorePosts: boolean
/**
* Posts for stream
*
* @type {{[postId: string]: Post}}
* @memberof IStreamComponentProps
*/
posts?: {[postId: string]: Post}
posts: {[postId: string]: Post}
/**
* Router match property
*/
match: any
}

View File

@@ -1,3 +1,4 @@
import { Post } from 'core/domain/posts'
export interface IStreamComponentState {
@@ -34,7 +35,15 @@ export interface IStreamComponentState {
divided: boolean
/**
* The tile of top bar
* If there is more post to show
*
* @type {boolean}
* @memberof IStreamComponentState
*/
hasMorePosts: boolean
/**
* The title of top bar
*
* @type {string}
* @memberof IStreamComponentState

View File

@@ -5,15 +5,18 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'
import FlatButton from 'material-ui/FlatButton'
import { grey400, grey800, darkBlack, lightBlack } from 'material-ui/styles/colors'
import { grey400, grey800, darkBlack, lightBlack,tealA400 } from 'material-ui/styles/colors'
import CircularProgress from 'material-ui/CircularProgress'
import SvgCamera from 'material-ui/svg-icons/image/photo-camera'
import Paper from 'material-ui/Paper'
import { List, ListItem } from 'material-ui/List'
import InfiniteScroll from 'react-infinite-scroller'
// - Import app components
import PostComponent from 'components/post'
import PostWriteComponent from 'components/postWrite'
import UserAvatarComponent from 'components/userAvatar'
import LoadMoreProgressComponent from 'layouts/loadMoreProgress'
// - Import API
import * as PostAPI from 'api/PostAPI'
@@ -23,6 +26,7 @@ import * as globalActions from 'actions/globalActions'
import { IStreamComponentProps } from './IStreamComponentProps'
import { IStreamComponentState } from './IStreamComponentState'
import { Post } from 'core/domain/posts'
// - Create StreamComponent component class
export class StreamComponent extends Component<IStreamComponentProps,IStreamComponentState> {
@@ -82,7 +86,12 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
/**
* The title of home header
*/
homeTitle: props.homeTitle!
homeTitle: props.homeTitle!,
/**
* If there is more post to show {true} or not {false}
*/
hasMorePosts: true
}
// Binding functions to `this`
@@ -115,13 +124,13 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
})
}
/**
* Create a list of posts
* @return {DOM} posts
*/
/**
* Create a list of posts
* @return {DOM} posts
*/
postLoad = () => {
let { posts ,match }: any = this.props
let { posts ,match } = this.props
let {tag} = match.params
if (posts === undefined || !(Object.keys(posts).length > 0)) {
@@ -139,7 +148,7 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
Object.keys(posts).forEach((postId) => {
if (tag) {
let regex = new RegExp('#' + tag,'g')
let postMatch = posts[postId].body.match(regex)
let postMatch = posts[postId].body!.match(regex)
if (postMatch !== null) {
parsedPosts.push({ ...posts[postId]})
}
@@ -177,6 +186,14 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
}
/**
* Scroll loader
*/
scrollLoad = (page: number) => {
const {loadStream} = this.props
loadStream(page, 10)
}
componentWillMount () {
const {setHomeTitle} = this.props
setHomeTitle!()
@@ -188,14 +205,18 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
*/
render () {
let postList: any = this.postLoad()
const {tag, displayWriting } = this.props
const {tag, displayWriting, hasMorePosts } = this.props
const postList = this.postLoad() as {evenPostList: Post[], oddPostList: Post[], divided: boolean} | any
return (
<div >
<InfiniteScroll
pageStart={0}
loadMore={this.scrollLoad}
hasMore={hasMorePosts}
useWindow={true}
loader={ <LoadMoreProgressComponent />}
>
<div className='grid grid__gutters grid__1of2 grid__space-around animate-top'>
<div className='grid-cell animate-top' style= {{maxWidth: '530px', minWidth: '280px'}}>
{displayWriting && !tag
? (<PostWriteComponent open={this.state.openPostWrite} onRequestClose={this.handleClosePostWrite} edit={false} >
@@ -225,9 +246,10 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
</div>
</div>)
: ''}
</div>
</div>
</InfiniteScroll>
)
}
}
@@ -254,6 +276,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IStreamComponentProps) => {
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IStreamComponentProps) => {
const {post} = state
return {
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : ''
@@ -261,4 +284,4 @@ const mapStateToProps = (state: any, ownProps: IStreamComponentProps) => {
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(StreamComponent as any))
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(StreamComponent as any) as any)

View File

@@ -1,6 +1,6 @@
import { User } from 'core/domain/users'
import { Circle } from 'core/domain/circles/circle'
import { UserFollower } from 'core/domain/circles'
import { UserTie } from 'core/domain/circles'
export interface IUserBoxComponentProps {
@@ -18,7 +18,7 @@ export interface IUserBoxComponentProps {
* @type {User}
* @memberof IUserBoxComponentProps
*/
user: UserFollower
user: UserTie
/**
* Circles
@@ -36,6 +36,11 @@ export interface IUserBoxComponentProps {
*/
userBelongCircles?: string[]
/**
* Whether current user followed this user
*/
isFollowed: boolean
/**
* The number of circles
*
@@ -80,7 +85,7 @@ export interface IUserBoxComponentProps {
*
* @memberof IUserBoxComponentProps
*/
addFollowingUser?: (cid: string,user: UserFollower) => any
addFollowingUser?: (cid: string,user: UserTie) => any
/**
* Delete

View File

@@ -2,12 +2,20 @@
export interface IUserBoxComponentState {
/**
* Add new circle button is disabled {true} or not {false}
* Create new circle button is disabled {true} or not {false}
*
* @type {boolean}
* @memberof IUserBoxComponentState
*/
disabledAddCircle: boolean
disabledCreateCircle: boolean
/**
* The button of add user in a circle is disabled {true} or not {false}
*
* @type {boolean}
* @memberof IUserBoxComponentState
*/
disabledAddToCircle: boolean
/**
* Circle name
@@ -32,4 +40,15 @@ export interface IUserBoxComponentState {
* @memberof IUserBoxComponentState
*/
open: boolean
/**
* Whether current user changed the selected circles for referer user
*
*/
disabledDoneCircles: boolean
/**
* Keep selected circles for refere user
*/
selectedCircles: string[]
}

View File

@@ -11,12 +11,17 @@ import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import Checkbox from 'material-ui/Checkbox'
import TextField from 'material-ui/TextField'
import { Dialog } from 'material-ui'
import SvgAdd from 'material-ui/svg-icons/content/add'
import IconButton from 'material-ui/IconButton'
import { grey400, grey800, darkBlack, lightBlack } from 'material-ui/styles/colors'
// - Import app components
import UserAvatar from 'components/userAvatar'
// - Import API
import CircleAPI from 'api/CircleAPI'
import StringAPI from 'api/StringAPI'
// - Import actions
import * as circleActions from 'actions/circleActions'
@@ -24,21 +29,22 @@ import * as circleActions from 'actions/circleActions'
import { IUserBoxComponentProps } from './IUserBoxComponentProps'
import { IUserBoxComponentState } from './IUserBoxComponentState'
import { User } from 'core/domain/users'
import { UserFollower, Circle } from 'core/domain/circles'
import { UserTie, Circle } from 'core/domain/circles'
import { ServerRequestType } from 'constants/serverRequestType'
/**
* Create component class
*/
export class UserBoxComponent extends Component<IUserBoxComponentProps,IUserBoxComponentState> {
export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBoxComponentState> {
static propTypes = {
/**
* User identifier
*/
/**
* User identifier
*/
userId: PropTypes.string,
/**
* User information
*/
/**
* User information
*/
user: PropTypes.object
}
@@ -53,207 +59,297 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps,IUserBoxC
},
followButton: {
position: 'absolute',
bottom: '8px',
bottom: '30px',
left: 0,
right: 0
},
dialog: {
width: '',
maxWidth: '280px',
borderRadius: '4px'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: IUserBoxComponentProps) {
super(props)
// Defaul state
const { userBelongCircles, circles } = this.props
// Defaul state
this.state = {
/**
* It will be true if user follow popover is open
*/
/**
* It will be true if user follow popover is open
*/
open: false,
/**
* The value of circle input
*/
/**
* The value of circle input
*/
circleName: '',
/**
* It will be true if the text field for adding group is empty
*/
disabledAddCircle: true
/**
* It will be true if the text field for adding group is empty
*/
disabledCreateCircle: true,
/**
* The button of add user in a circle is disabled {true} or not {false}
*/
disabledAddToCircle: true,
/**
* Keep selected circles for refere user
*/
selectedCircles: userBelongCircles ? userBelongCircles!.slice() : [],
/**
* Whether current user changed the selected circles for referer user
*/
disabledDoneCircles: true
}
// Binding functions to `this`
// Binding functions to `this`
this.handleChangeName = this.handleChangeName.bind(this)
this.handleCreateCricle = this.handleCreateCricle.bind(this)
this.onCreateCircle = this.onCreateCircle.bind(this)
this.handleFollowUser = this.handleFollowUser.bind(this)
this.handleFollowUser = this.handleFollowUser.bind(this)
}
handleFollowUser = (checked: boolean,cid: string) => {
const {userId,user} = this.props
const {avatar,fullName} = user
/**
* Handle follow user
*/
handleFollowUser = (checked: boolean, cid: string) => {
const { userId, user } = this.props
const { avatar, fullName } = user
if (checked) {
this.props.addFollowingUser!(cid,{avatar,userId,fullName})
this.props.addFollowingUser!(cid, { avatar, userId, fullName })
} else {
this.props.deleteFollowingUser!(cid,userId)
this.props.deleteFollowingUser!(cid, userId)
}
}
/**
* Handle create circle
*
*
* @memberof UserBox
*/
handleCreateCricle = () => {
const {circleName} = this.state
if (circleName && circleName.trim() !== '' ) {
this.props.createCircle!(this.state.circleName)
this.setState({
circleName: '',
disabledAddCircle: true
})
}
}
/**
* Handle change group name input to the state
*
*
* @memberof UserBox
*/
handleChangeName = (event: any) => {
this.setState({
circleName: event.target.value,
disabledAddCircle: (event.target.value === undefined || event.target.value.trim() === '')
})
}
/**
* Handle touch tab on follow popover
*
*
* @memberof UserBox
*/
handleTouchTap = (event: any) => {
// This prevents ghost click.
event.preventDefault()
this.setState({
open: true,
anchorEl: event.currentTarget
})
}
/**
* Handle close follow popover
*
*
* @memberof UserBox
*/
handleRequestClose = () => {
/**
* Handle request close for add circle box
*/
onRequestCloseAddCircle = () => {
this.setState({
open: false
})
}
/**
* Handle request open for add circle box
*/
onRequestOpenAddCircle = () => {
this.setState({
open: true
})
}
/**
* Create a new circle
*/
onCreateCircle = () => {
const { circleName } = this.state
if (circleName && circleName.trim() !== '') {
this.props.createCircle!(this.state.circleName)
this.setState({
circleName: '',
disabledCreateCircle: true
})
}
}
/**
* Handle change group name input to the state
*/
handleChangeName = (event: any) => {
this.setState({
circleName: event.target.value,
disabledCreateCircle: (event.target.value === undefined || event.target.value.trim() === '')
})
}
handleSelectCircle = (event: object, isInputChecked: boolean, circleId: string) => {
const { userBelongCircles, circles } = this.props
let selectedCircles = this.state.selectedCircles
if (isInputChecked) {
selectedCircles = [
...selectedCircles,
circleId
]
} else {
const circleIndex = selectedCircles.indexOf(circleId)
selectedCircles.splice(circleIndex, 1)
}
this.setState({
selectedCircles: selectedCircles,
disabledDoneCircles: !this.selectedCircleChange(selectedCircles)
})
}
/**
* Handle follow user
*/
onFollowUser = (event: any) => {
// This prevents ghost click
event.preventDefault()
this.onRequestOpenAddCircle()
}
/**
* Add user to the circle/circles
*/
onAddToCircle = () => {
}
/**
* Create a circle list of user which belong to
*/
circleList = () => {
let { circles, userId, userBelongCircles } = this.props
if (circles) {
return Object.keys(circles).map((key, index) => {
if (key.trim() !== '-Followers') {
let isBelong = userBelongCircles!.indexOf(key) > -1
return <Checkbox
key={key}
style={{ padding: '10px' }}
label={circles![key].name}
labelStyle={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
onCheck={(evt,checked) => this.handleFollowUser(checked,key)}
checked={isBelong}
/>
}
return Object.keys(circles).map((circleId, index) => {
const {selectedCircles} = this.state
let isBelong = selectedCircles!.indexOf(circleId) > -1
// Create checkbox for selected/unselected circle
return <Checkbox
key={circleId}
style={{ padding: '10px' }}
label={circles![circleId].name}
labelStyle={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
onCheck={(event: object, isInputChecked: boolean) => this.handleSelectCircle(event, isInputChecked, circleId)}
checked={isBelong}
/>
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
/**
* Check if the the selected circles changed
*/
selectedCircleChange = (selectedCircles: string[]) => {
let isChanged = false
const { userBelongCircles, circles } = this.props
if (selectedCircles.length === userBelongCircles!.length) {
for (let circleIndex: number = 0; circleIndex < selectedCircles.length; circleIndex++) {
const selectedCircleId = selectedCircles[circleIndex]
if (!(userBelongCircles!.indexOf(selectedCircleId) > -1)) {
isChanged = true
break
}
}
} else {
isChanged = true
}
return isChanged
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render () {
const {disabledDoneCircles} = this.state
const writeActions = [
<FlatButton
label='Cancel'
primary={true}
keyboardFocused={false}
onTouchTap={this.onRequestCloseAddCircle}
style={{ color: grey800 }}
/>,
<FlatButton
label='Done'
primary={true}
keyboardFocused={false}
disabled={disabledDoneCircles}
onTouchTap={this.onCreateCircle}
/>
]
const { isFollowed } = this.props
return (
<Paper style={this.styles.paper} zDepth={1} className='grid-cell'>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
height: '100%',
position: 'relative',
padding: '30px'
<Paper style={this.styles.paper} zDepth={1} className='grid-cell'>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
height: '100%',
position: 'relative',
paddingTop: 20
}}>
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} style={{cursor: 'pointer'}}>
<UserAvatar
fullName={this.props.fullName!}
fileName={this.props.avatar!}
size={90}
/>
</div>
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} className='people__name' style={{cursor: 'pointer'}}>
<div>
{this.props.user.fullName}
</div>
</div>
<div style={this.styles.followButton as any}>
<FlatButton
label={(this.props.belongCirclesCount && this.props.belongCirclesCount < 1) ? 'Follow'
: (this.props.belongCirclesCount! > 1 ? `${this.props.belongCirclesCount} Circles` : ((this.props.firstBelongCircle) ? this.props.firstBelongCircle.name : 'Follow'))}
primary={true}
onTouchTap={this.handleTouchTap}
/>
</div>
</div>
<Popover
open={this.state.open}
anchorEl={this.state.anchorEl}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
onRequestClose={this.handleRequestClose}
animation={PopoverAnimationVertical}
>
<Menu >
<div style={{
position: 'relative',
display: 'block',
maxHeight: '220px'
}}>
<div style={{ overflowY: 'auto', height: '100%' }}>
{this.circleList()}
}}>
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} style={{ cursor: 'pointer' }}>
<UserAvatar
fullName={this.props.fullName!}
fileName={this.props.avatar!}
size={90}
/>
</div>
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} className='people__name' style={{ cursor: 'pointer' }}>
<div>
{this.props.user.fullName}
</div>
</div>
<div style={this.styles.followButton as any}>
<FlatButton
label={!isFollowed ? 'Follow'
: (this.props.belongCirclesCount! > 1 ? `${this.props.belongCirclesCount} Circles` : ((this.props.firstBelongCircle) ? this.props.firstBelongCircle.name : 'Follow'))}
primary={true}
onTouchTap={this.onFollowUser}
/>
</div>
</div>
<Dialog
key={this.props.userId || 0}
actions={writeActions}
modal={false}
open={this.state.open}
contentStyle={this.styles.dialog}
onRequestClose={this.onRequestCloseAddCircle}
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
bodyStyle={{ padding: 0 }}
autoDetectWindowHeight={false}
actionsContainerStyle={{ borderTop: '1px solid rgb(224, 224, 224)' }}
</div>
</div>
<div style={{ padding: '10px' }}>
<TextField
hintText='Group name'
onChange={this.handleChangeName}
value={this.state.circleName}
/><br />
<FlatButton label='ADD' primary={true} disabled={this.state.disabledAddCircle} onClick={this.handleCreateCricle} />
</div>
</Menu>
</Popover>
</Paper>
>
<div style={{
position: 'relative',
display: 'block',
maxHeight: '220px'
}}>
<div style={{ overflowY: 'auto', height: '100%' }}>
{this.circleList()}
</div>
</div>
<div style={{ padding: '10px' }}>
<TextField
hintText='Group name'
onChange={this.handleChangeName}
value={this.state.circleName}
/>
<div className='user-box__add-circle'>
<IconButton onClick={this.onCreateCircle} tooltip='Create circle' disabled={this.state.disabledCreateCircle}>
<SvgAdd />
</IconButton>
</div>
<br />
</div>
</Dialog>
</Paper>
)
}
}
@@ -267,8 +363,8 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps,IUserBoxC
const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps) => {
return {
createCircle: (name: string) => dispatch(circleActions.dbAddCircle(name)),
addFollowingUser: (cid: string,user: UserFollower) => dispatch(circleActions.dbAddFollowingUser(cid,user)),
deleteFollowingUser: (cid: string ,followingId: string) => dispatch(circleActions.dbDeleteFollowingUser(cid,followingId)),
addFollowingUser: (circleIds: string[], user: UserTie) => dispatch(circleActions.dbUpdateUserInCircles(circleIds, user)),
deleteFollowingUser: (cid: string, followingId: string) => dispatch(circleActions.dbDeleteFollowingUser(followingId)),
goTo: (url: string) => dispatch(push(url))
}
@@ -281,13 +377,17 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IUserBoxComponentProps) => {
const { uid } = state.authorize
const circles: {[circleId: string]: Circle} = state.circle ? (state.circle.userCircles[uid] || {}) : {}
const userBelongCircles = CircleAPI.getUserBelongCircles(circles,ownProps.userId)
const { circle, authorize, server } = state
const { uid } = authorize
const { request } = server
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
const userBelongCircles = circle ? (circle.userTies[ownProps.userId] ? circle.userTies[ownProps.userId].circleIdList : []) : []
const isFollowed = userBelongCircles.length > 0
return {
circles: circles,
userBelongCircles: userBelongCircles || [],
isFollowed,
circles,
userBelongCircles,
belongCirclesCount: userBelongCircles.length || 0,
firstBelongCircle: userBelongCircles ? (circles ? circles[userBelongCircles[0]] : {}) : {},
avatar: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].avatar || '' : '',

View File

@@ -1,5 +1,5 @@
import { User } from 'core/domain/users'
import { UserFollower } from 'core/domain/circles'
import { UserTie } from 'core/domain/circles'
export interface IUserBoxListComponentProps {
@@ -9,7 +9,7 @@ export interface IUserBoxListComponentProps {
* @type {{[userId: string]: User}}
* @memberof IUserBoxListComponentProps
*/
users: {[userId: string]: UserFollower}
users: {[userId: string]: UserTie}
/**
* User identifier

View File

@@ -8,6 +8,7 @@ import { List } from 'material-ui/List'
import CircleComponent from 'components/circle'
import { IYourCirclesComponentProps } from './IYourCirclesComponentProps'
import { IYourCirclesComponentState } from './IYourCirclesComponentState'
import { Circle } from 'core/domain/circles';
// - Import API
@@ -97,10 +98,12 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IYourCirclesComponentP
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IYourCirclesComponentProps) => {
const {circle, authorize, server} = state
const { uid } = state.authorize
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
return {
uid,
circles: state.circle ? state.circle.userCircles[uid] : {}
circles
}
}

View File

@@ -9,4 +9,13 @@ export enum CircleActionType {
CLOSE_CIRCLE_SETTINGS = 'CLOSE_CIRCLE_SETTINGS',
ADD_FOLLOWING_USER = 'ADD_FOLLOWING_USER',
DELETE_FOLLOWING_USER = 'DELETE_FOLLOWING_USER',
UPDATE_USER_TIE = 'UPDATE_USER_TIE',
ADD_USER_TIE_LIST = 'ADD_USER_TIE_LIST',
ADD_USER_TIED_LIST = 'ADD_USER_TIED_LIST',
DELETE_USER_FROM_CIRCLE = 'DELETE_USER_FROM_CIRCLE',
SHOW_SELECT_CIRCLE_BOX = 'SHOW_SELECT_CIRCLE_BOX',
HIDE_SELECT_CIRCLE_BOX = 'HIDE_SELECT_CIRCLE_BOX',
SHOW_FOLLOWING_USER_LOADING = 'SHOW_FOLLOWING_USER_LOADING',
HIDE_FOLLOWING_USER_LOADING = 'HIDE_FOLLOWING_USER_LOADING'
}

View File

@@ -1,6 +1,14 @@
export enum PostActionType {
ADD_IMAGE_POST = 'ADD_IMAGE_POST',
HAS_MORE_DATA_STREAM = 'HAS_MORE_DATA_STREAM',
NOT_MORE_DATA_STREAM = 'NOT_MORE_DATA_STREAM',
REQUEST_PAGE_STREAM = 'REQUEST_PAGE_STREAM',
LAST_POST_STREAM = 'LAST_POST_STREAM',
HAS_MORE_DATA_PROFILE = 'HAS_MORE_DATA_PROFILE',
NOT_MORE_DATA_PROFILE = 'NOT_MORE_DATA_PROFILE',
REQUEST_PAGE_PROFILE = 'REQUEST_PAGE_PROFILE',
LAST_POST_PROFILE = 'LAST_POST_PROFILE',
ADD_VIDEO_POST = 'ADD_VIDEO_POST',
ADD_POST = 'ADD_POST',
UPDATE_POST = 'UPDATE_POST',
@@ -8,4 +16,3 @@
ADD_LIST_POST = 'ADD_LIST_POST',
CLEAR_ALL_DATA_POST = 'CLEAR_ALL_DATA_POST'
}

View File

@@ -0,0 +1,8 @@
export enum ServerActionType {
ADD_REQUEST = 'ADD_REQUEST',
DELETE_REQUEST = 'DELETE_REQUEST',
ERROR_REQUEST = 'ERROR_REQUEST',
OK_REQUEST = 'OK_REQUEST',
CLEAR_ALL_DATA_REQUEST = 'CLEAR_ALL_DATA_REQUEST'
}

View File

@@ -0,0 +1,6 @@
export enum ServerRequestType {
CircleAddToCircle = 'CircleAddToCircle',
CircleFollowUser = 'CircleFollowUser',
CircleCreateTieUser = 'CircleCreateTieUser'
}

View File

@@ -7,5 +7,9 @@ export enum UserActionType {
CLEAR_ALL_DATA_USER = 'CLEAR_ALL_DATA_USER',
OPEN_EDIT_PROFILE = 'OPEN_EDIT_PROFILE',
CLOSE_EDIT_PROFILE = 'CLOSE_EDIT_PROFILE',
ADD_PEOPLE_INFO = 'ADD_PEOPLE_INFO'
ADD_PEOPLE_INFO = 'ADD_PEOPLE_INFO',
HAS_MORE_DATA_PEOPLE = 'HAS_MORE_DATA_PEOPLE',
NOT_MORE_DATA_PEOPLE = 'NOT_MORE_DATA_PEOPLE',
REQUEST_PAGE_PEOPLE = 'REQUEST_PAGE_PEOPLE',
LAST_USER_PEOPLE = 'LAST_POST_PEOPLE'
}

View File

@@ -1,4 +1,3 @@
import { UserFollower } from './userFollower'
import { BaseDomain } from 'core/domain/common'
export class Circle extends BaseDomain {
@@ -35,12 +34,9 @@ export class Circle extends BaseDomain {
*/
public name: string
/**
* The users in a circle
*
* @type {string}
* @memberof User
*/
public users: {[userId: string]: UserFollower}
/**
* Whether circle setting is open
*/
public openCircleSettings?: boolean
}

View File

@@ -1,7 +1,7 @@
import {Circle} from './circle'
import {UserFollower} from './userFollower'
import { Circle } from './circle'
import { UserTie } from './UserTie'
export {
Circle,
UserFollower
UserTie
}

View File

@@ -1,14 +1,15 @@
import { BaseDomain } from 'core/domain/common'
export class UserFollower extends BaseDomain {
export class UserTie extends BaseDomain {
constructor (
/**
* User identifier
*
* @type {string}
* @memberof UserFollower
* @memberof UserTie
*/
userId?: string
public userId?: string,
/**
* Circle creation date time
@@ -16,30 +17,38 @@ export class UserFollower extends BaseDomain {
* @type {Date}
* @memberof Circle
*/
public creationDate?: number
public creationDate?: number,
/**
* User full name
*
* @type {string}
* @memberof UserFollower
* @memberof UserTie
*/
public fullName: string
public fullName?: string,
/**
* Avatar URL address
*
* @type {string}
* @memberof UserFollower
* @memberof UserTie
*/
public avatar: string
public avatar?: string,
/**
* If following user approved {true} or not {false}
*
* @type {Boolean}
* @memberof UserFollower
* @memberof UserTie
*/
public approved?: Boolean
public approved?: Boolean,
/**
* List of circles identifire which this user belong to
*/
public circleIdList?: string[]
) {
super()
}
}

View File

@@ -0,0 +1,62 @@
import { BaseDomain } from 'core/domain/common'
export class Graph extends BaseDomain {
constructor (
/**
* Left node of graph
*
* @type {string}
* @memberof Graph
*/
public leftNode: string,
/**
* Graph relationship type
*
* @type {number}
* @memberof Graph
*/
public edgeType: string,
/**
* Right node of graph
*
* @type {string}
* @memberof Graph
*/
public rightNode: string,
/**
* Graph left node metadata
*
* @memberof Graph
*/
public LeftMetadata: any,
/**
* Graph right node metadata
*
* @memberof Graph
*/
public rightMetadata: any,
/**
* Graph metadata
*
* @type {string}
* @memberof Graph
*/
public graphMetadata: any,
/**
* Graph node identifier
*
* @type {string}
* @memberof Graph
*/
public nodeId?: string
) { super() }
}

View File

@@ -0,0 +1,5 @@
import {Graph} from './graph'
export {
Graph
}

View File

@@ -8,7 +8,7 @@ export class User extends BaseDomain {
* @type {string}
* @memberof User
*/
public fullName: string
public fullName: string
/**
* User avatar address
@@ -16,7 +16,7 @@ export class User extends BaseDomain {
* @type {string}
* @memberof User
*/
public avatar: string
public avatar: string
/**
* Email of the user
@@ -24,7 +24,7 @@ export class User extends BaseDomain {
* @type {string}
* @memberof User
*/
public email?: string | null
public email?: string | null
/**
* Password of the user
@@ -32,7 +32,7 @@ export class User extends BaseDomain {
* @type {string}
* @memberof User
*/
public password?: string | null
public password?: string | null
/**
* User identifier
@@ -40,7 +40,7 @@ export class User extends BaseDomain {
* @type {string}
* @memberof User
*/
public userId?: string | null
public userId?: string | null
/**
* User creation date
@@ -48,6 +48,6 @@ export class User extends BaseDomain {
* @type {number}
* @memberof User
*/
public creationDate: number
public creationDate: number
}

View File

@@ -2,14 +2,6 @@ import { BaseDomain } from 'core/domain/common'
export class Vote extends BaseDomain {
/**
* Vote identifier
*
* @type {string}
* @memberof Vote
*/
public id?: string | null
/**
* Post identifire which vote on
*

View File

@@ -1,6 +1,7 @@
//#region Interfaces
import { IServiceProvider } from 'core/factories'
import { injectable } from 'inversify'
import {
IAuthorizeService,
ICircleService,
@@ -33,7 +34,7 @@ import {
} from 'data/firestoreClient/services'
//#endregion
@injectable()
export class ServiceProvide implements IServiceProvider {
/**

View File

@@ -1,5 +1,5 @@
import { User } from 'core/domain/users'
import { Circle, UserFollower } from 'core/domain/circles'
import { Circle, UserTie } from 'core/domain/circles'
/**
* Circle service interface
@@ -10,8 +10,6 @@ import { Circle, UserFollower } from 'core/domain/circles'
export interface ICircleService {
addCircle: (userId: string, circle: Circle) => Promise<string>
addFollowingUser: (userId: string, circleId: string, userCircle: User, userFollower: UserFollower, userFollowingId: string) => Promise<void>
deleteFollowingUser: (userId: string, circleId: string,userFollowingId: string) => Promise<void>
updateCircle: (userId: string, circleId: string, circle: Circle) => Promise<void>
deleteCircle: (userId: string, circleId: string) => Promise<void>
getCircles: (userId: string) => Promise<{ [circleId: string]: Circle }>

View File

@@ -0,0 +1,35 @@
import { User, Profile } from 'core/domain/users'
import { UserTie } from 'core/domain/circles'
/**
* User tie service interface
*
* @export
* @interface IUserTieService
*/
export interface IUserTieService {
/**
* Tie users
*/
tieUseres: (userTieSenderInfo: UserTie, userTieReceiveInfo: UserTie, circleIds: string[])
=> Promise<void>
/**
* Remove users' tie
*/
removeUsersTie: (firstUserId: string, secondUserId: string)
=> Promise<void>
/**
* Get user ties
*/
getUserTies: (userId: string)
=> Promise<{[userId: string]: UserTie}>
/**
* Get the users who tied current user
*/
getUserTieSender: (userId: string)
=> Promise<{[userId: string]: UserTie}>
}

View File

@@ -1,5 +1,7 @@
import { ICircleService } from './ICircleService'
import { IUserTieService } from './IUserTieService'
export {
ICircleService
ICircleService,
IUserTieService
}

View File

@@ -13,6 +13,6 @@ export interface ICommentService {
addComment: (comment: Comment) => Promise<string>
getComments: (postId: string, callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void) => void
updateComment: (comment: Comment) => Promise<void>
deleteComment: (commentId: string, postId: string) => Promise<void>
deleteComment: (commentId: string) => Promise<void>
}

View File

@@ -11,6 +11,17 @@ export interface IPostService {
addPost: (post: Post) => Promise<string>
updatePost: (post: Post) => Promise<void>
deletePost: (postId: string) => Promise<void>
getPosts: (userId: string) => Promise<{ [postId: string]: Post }>
getPosts: (currentUserId: string,lastPostId: string, page: number, limit: number)
=> Promise<{posts: {[postId: string]: Post }[], newLastPostId: string}>
/**
* Get list of post by user identifier
*/
getPostsByUserId: (userId: string, lastPostId?: string, page?: number, limit?: number)
=> Promise<{ posts: { [postId: string]: Post }[], newLastPostId: string }>
/**
* Get post by the post identifier
*/
getPostById: (postId: string) => Promise<Post>
}

View File

@@ -9,5 +9,6 @@ import { User, Profile } from 'core/domain/users'
export interface IUserService {
getUserProfile: (userId: string) => Promise<Profile>
updateUserProfile: (userId: string, profile: Profile) => Promise<void>
getUsersProfile: (userId: string, lastKey?: string, numberOfItems?: number) => Promise<{[userId: string]: Profile}>
getUsersProfile: (userId: string, lastUserId?: string, page?: number, limit?: number)
=> Promise<{ users: { [userId: string]: Profile }[], newLastUserId: string }>
}

View File

@@ -10,5 +10,5 @@ import { Vote } from 'core/domain/votes'
export interface IVoteService {
addVote: (vote: Vote) => Promise<string>
getVotes: (postId: string) => Promise<{[postId: string]: {[voteId: string]: Vote}}>
deleteVote: (vote: Vote) => Promise<void>
deleteVote: (userId: string, voteId: string) => Promise<void>
}

View File

@@ -0,0 +1,17 @@
/**
* InversifyJS need to use the type as identifiers at runtime.
* We use symbols as identifiers but you can also use classes and or string literals.
*/
export const SocialProviderTypes = {
AuthorizeService: Symbol('AuthorizeService'),
UserTieService: Symbol('UserTieService'),
CircleService: Symbol('CircleService'),
CommentService: Symbol('CommentService'),
CommonService: Symbol('CommonService'),
StorageService: Symbol('StorageService'),
ImageGalleryService: Symbol('ImageGalleryService'),
NotificationService: Symbol('NotificationService'),
PostService: Symbol('PostService'),
UserService: Symbol('UserService'),
VoteService: Symbol('VoteService')
}

View File

@@ -1,27 +0,0 @@
import firebase from 'firebase'
try {
let config = {
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID
}
firebase.initializeApp(config)
} catch (error) {
console.log('=========Firebase initializer==============')
console.log(error)
console.log('====================================')
}
// - Storage reference
export let storageRef = firebase.storage().ref()
// - Database authorize
export let firebaseAuth = firebase.auth
export let firebaseRef = firebase.database().ref()
// - Firebase default
export default firebase

View File

@@ -1,266 +0,0 @@
import moment from 'moment'
import { Profile } from 'core/domain/users'
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { IAuthorizeService } from 'core/services/authorize'
import { User, UserProvider } from 'core/domain/users'
import { LoginUser, RegisterUserResult } from 'core/domain/authorize'
import { SocialError } from 'core/domain/common'
import { OAuthType } from 'core/domain/authorize/oauthType'
/**
* Firbase authorize service
*
* @export
* @class AuthorizeService
* @implements {IAuthorizeService}
*/
export class AuthorizeService implements IAuthorizeService {
/**
* Login the user
*
* @returns {Promise<LoginUser>}
* @memberof IAuthorizeService
*/
public login: (email: string, password: string) => Promise<LoginUser> = (email, password) => {
return new Promise<LoginUser>((resolve, reject) => {
firebaseAuth()
.signInWithEmailAndPassword(email, password)
.then((result) => {
resolve(new LoginUser(result.uid, result.emailVerified))
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Logs out the user
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public logout: () => Promise<void> = () => {
return new Promise<void>((resolve, reject) => {
firebaseAuth()
.signOut()
.then((result) => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Register a user
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public registerUser: (user: User) => Promise<RegisterUserResult> = (user) => {
return new Promise<RegisterUserResult>((resolve, reject) => {
firebaseAuth()
.createUserWithEmailAndPassword(user.email as string, user.password as string)
.then((signupResult) => {
const {uid, email} = signupResult
this.storeUserInformation(uid,email,user.fullName,'').then(resolve)
})
.catch((error: any) => reject(new SocialError(error.code, error.message)))
})
}
/**
* Update user password
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public updatePassword: (newPassword: string) => Promise<void> = (newPassword) => {
return new Promise<void>((resolve, reject) => {
let user = firebaseAuth().currentUser
if (user) {
user.updatePassword(newPassword).then(() => {
// Update successful.
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
}
})
}
/**
* On user authorization changed event
*
* @memberof IAuthorizeService
*/
public onAuthStateChanged: (callBack: (isVerifide: boolean, user: User) => void) => any = (callBack) => {
firebaseAuth().onAuthStateChanged( (user: any) => {
let isVerifide = false
if (user) {
if (user.emailVerified || user.providerData[0].providerId.trim() !== 'password') {
isVerifide = true
} else {
isVerifide = false
}
}
callBack(isVerifide,user)
})
}
/**
* Reset user password
*
* @memberof AuthorizeService
*/
public resetPassword: (email: string) => Promise<void> = (email) => {
return new Promise<void>((resolve,reject) => {
let auth = firebaseAuth()
auth.sendPasswordResetEmail(email).then(function () {
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Send verfication email to user email
*
* @memberof AuthorizeService
*/
public sendEmailVerification: () => Promise<void> = () => {
return new Promise<void>((resolve,reject) => {
let auth = firebaseAuth()
const user = auth.currentUser
if (user) {
user.sendEmailVerification().then(() => {
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
} else {
reject(new SocialError('authorizeService/nullException', 'User was null!'))
}
})
}
public loginWithOAuth: (type: OAuthType) => Promise<LoginUser> = (type) => {
return new Promise<LoginUser>((resolve,reject) => {
let provider: any
switch (type) {
case OAuthType.GITHUB:
provider = new firebaseAuth.GithubAuthProvider()
break
case OAuthType.FACEBOOK:
provider = new firebaseAuth.FacebookAuthProvider()
break
case OAuthType.GOOGLE:
provider = new firebaseAuth.GoogleAuthProvider()
break
default:
throw new SocialError('authorizeService/loginWithOAuth','None of OAuth type is matched!')
}
firebaseAuth().signInWithPopup(provider).then((result) => {
// This gives you a GitHub Access Token. You can use it to access the GitHub API.
let token = result.credential.accessToken
// The signed-in user info.
const {user} = result
const {credential} = result
const {uid, displayName, email, photoURL} = user
const {accessToken, provider, providerId} = credential
this.storeUserProviderData(uid,email,displayName,photoURL,providerId,provider,accessToken)
// this.storeUserInformation(uid,email,displayName,photoURL).then(resolve)
resolve(new LoginUser(user.uid,true,providerId,displayName,email,photoURL))
}).catch(function (error: any) {
// Handle Errors here.
let errorCode = error.code
let errorMessage = error.message
// The email of the user's account used.
let email = error.email
// The firebase.auth.AuthCredential type that was used.
let credential = error.credential
})
})
}
/**
* Store user information
*
* @private
* @memberof AuthorizeService
*/
private storeUserInformation = (userId: string, email: string, fullName: string, avatar: string) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
firebaseRef.child(`users/${userId}/info`)
.set(new Profile(
avatar,
fullName,
'',
'',
moment().unix(),
email
))
.then((result) => {
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
})
}
/**
* Store user provider information
*
* @private
* @memberof AuthorizeService
*/
private storeUserProviderData = (
userId: string,
email: string,
fullName: string,
avatar: string,
providerId: string,
provider: string,
accessToken: string
) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
firebaseRef.child(`users/${userId}/providerInfo`)
.set(new UserProvider(
userId,
email,
fullName,
avatar,
providerId,
provider,
accessToken
))
.then((result) => {
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
})
}
}

View File

@@ -1,5 +0,0 @@
import { AuthorizeService } from './AuthorizeService'
export {
AuthorizeService
}

View File

@@ -1,118 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICircleService } from 'core/services/circles'
import { Circle, UserFollower } from 'core/domain/circles'
import { User } from 'core/domain/users'
/**
* Firbase circle service
*
* @export
* @class CircleService
* @implements {ICircleService}
*/
export class CircleService implements ICircleService {
public addCircle: (userId: string, circle: Circle)
=> Promise<string> = (userId, circle) => {
return new Promise<string>((resolve,reject) => {
let circleRef = firebaseRef.child(`users/${userId}/circles`).push(circle)
circleRef.then(() => {
resolve(circleRef.key as string)
})
})
}
public addFollowingUser: (userId: string, circleId: string, userCircle: User, userFollower: UserFollower, userFollowingId: string)
=> Promise<void> = (userId, circleId, userCircle, userFollower, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/circles/${circleId}/users/${userFollowingId}`] = userCircle
updates[`users/${userFollowingId}/circles/-Followers/users/${userId}`] = userFollower
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteFollowingUser: (userId: string, circleId: string, userFollowingId: string)
=> Promise<void> = (userId, circleId, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/circles/${circleId}/users/${userFollowingId}`] = null
updates[`users/${userFollowingId}/circles/-Followers/users/${userId}`] = null
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public updateCircle: (userId: string, circleId: string, circle: Circle)
=> Promise<void> = (userId, circleId, circle) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/circles/${circleId}`] = circle
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteCircle: (userId: string, circleId: string)
=> Promise<void> = (userId, circleId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/circles/${circleId}`] = null
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public getCircles: (userId: string) => Promise<{ [circleId: string]: Circle }> = (userId) => {
return new Promise<{ [circleId: string]: Circle }>((resolve,reject) => {
let circlesRef: any = firebaseRef.child(`users/${userId}/circles`)
circlesRef.once('value').then((snapshot: any) => {
let circles: any = snapshot.val() || {}
let parsedCircles: { [circleId: string]: Circle } = {}
Object.keys(circles).forEach((circleId) => {
parsedCircles[circleId] = {
id: circleId,
...circles[circleId]
}
})
resolve(parsedCircles)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { CircleService } from './CircleService'
export {
CircleService
}

View File

@@ -1,69 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICommentService } from 'core/services/comments'
import { Comment } from 'core/domain/comments'
/**
* Firbase comment service
*
* @export
* @class CommentService
* @implements {ICommentService}
*/
export class CommentService implements ICommentService {
public addComment: (postId: string, comment: Comment)
=> Promise<string> = (postId, comment) => {
return new Promise<string>((resolve,reject) => {
let commentRef: any = firebaseRef.child(`postComments/${postId}`).push(comment)
commentRef.then(() => {
resolve(commentRef.key)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getComments: (callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void)
=> void = (callback) => {
let commentsRef: any = firebaseRef.child(`postComments`)
commentsRef.on('value', (snapshot: any) => {
let comments: {[postId: string]: {[commentId: string]: Comment}} = snapshot!.val() || {}
callback(comments)
})
}
public updateComment: (commentId: string, postId: string, comment: Comment)
=> Promise<void> = (commentId, postId, comment) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`postComments/${postId}/${commentId}`] = comment
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public deleteComment: (commentId: string, postId: string)
=> Promise<void> = (commentId, postId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`postComments/${postId}/${commentId}`] = null
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { CommentService } from './CommentService'
export {
CommentService
}

View File

@@ -1,16 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICommonService } from 'core/services/common'
/**
* Firbase common service
*
* @export
* @class CommonService
* @implements {ICommonService}
*/
export class CommonService implements ICommonService {
}

View File

@@ -1,5 +0,0 @@
import { CommonService } from './CommonService'
export {
CommonService
}

View File

@@ -1,41 +0,0 @@
import { storageRef } from 'data/firebaseClient'
import { IStorageService } from 'core/services/files'
import { FileResult } from 'models/files/fileResult'
export class StorageService implements IStorageService {
/**
* Upload image on the server
* @param {file} file
* @param {string} fileName
*/
public uploadFile = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => {
return new Promise<FileResult>((resolve, reject) => {
// Create a storage refrence
let storegeFile = storageRef.child(`images/${fileName}`)
// Upload file
let task = storegeFile.put(file)
task.then((result) => {
resolve(new FileResult(result.downloadURL!,result.metadata.fullPath))
}).catch((error) => {
reject(error)
})
// Upload storage bar
task.on('state_changed', (snapshot: any) => {
let percentage: number = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
progress(percentage, true)
}, (error) => {
console.log('========== Upload Image ============')
console.log(error)
console.log('====================================')
}, () => {
progress(100, false)
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { StorageService } from './StorageService'
export {
StorageService
}

View File

@@ -1,112 +0,0 @@
import { FileResult } from 'models/files/fileResult'
// - Import react components
import { firebaseRef, firebaseAuth, storageRef } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { IImageGalleryService } from 'core/services/imageGallery'
import { Image } from 'core/domain/imageGallery'
import { IStorageService } from 'core/services/files'
import { IServiceProvider, ServiceProvide } from 'core/factories'
/**
* Firbase image gallery service
*
* @export
* @class ImageGalleryService
* @implements {IImageGalleryService}
*/
export class ImageGalleryService implements IImageGalleryService {
private readonly storageService: IStorageService
private readonly serviceProvider: IServiceProvider
constructor () {
this.serviceProvider = new ServiceProvide()
this.storageService = this.serviceProvider.createStorageService()
}
public getImageGallery: (userId: string)
=> Promise<Image[]> = (userId) => {
return new Promise<Image[]>((resolve,reject) => {
let imagesRef: any = firebaseRef.child(`userFiles/${userId}/files/images`)
imagesRef.once('value').then((snapshot: any) => {
let images = snapshot.val() || {}
let parsedImages: Image[] = []
Object.keys(images).forEach((imageId) => {
parsedImages.push({
id: imageId,
...images[imageId]
})
})
resolve(parsedImages)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public saveImage: (userId: string, image: Image)
=> Promise<string> = (userId, image) => {
return new Promise<string>((resolve,reject) => {
let imageRef = firebaseRef.child(`userFiles/${userId}/files/images`).push(image)
imageRef.then(() => {
resolve(imageRef.key!)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteImage: (userId: string, imageId: string)
=> Promise<void> = (userId, imageId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`userFiles/${userId}/files/images/${imageId}`] = null
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public uploadImage: (image: any, imageName: string, progressCallback: (percentage: number, status: boolean) => void)
=> Promise<FileResult> = (image, imageName, progressCallback) => {
return new Promise<FileResult>((resolve,reject) => {
this.storageService.uploadFile(image,imageName,progressCallback)
.then((result: FileResult) => {
resolve(result)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public downloadImage: (fileName: string)
=> Promise<string> = (fileName) => {
return new Promise<string>((resolve,reject) => {
// Create a reference to the file we want to download
let starsRef: any = storageRef.child(`images/${fileName}`)
// Get the download URL
starsRef.getDownloadURL().then((url: string) => {
resolve(url)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { ImageGalleryService } from './ImageGalleryService'
export {
ImageGalleryService
}

View File

@@ -1,24 +0,0 @@
import { AuthorizeService } from './authorize'
import { CircleService } from './circles'
import { CommentService } from './comments'
import { CommonService } from './common'
import { ImageGalleryService } from './imageGallery'
import { NotificationService } from './notifications'
import { PostService } from './posts'
import { UserService } from './users'
import { VoteService } from './votes'
import { StorageService } from './files'
export {
AuthorizeService,
CircleService,
CommentService,
CommonService,
ImageGalleryService,
NotificationService,
PostService,
UserService,
VoteService,
StorageService
}

View File

@@ -1,5 +0,0 @@
import { NotificationService } from './NotificationService'
export {
NotificationService
}

View File

@@ -1,69 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { Notification } from 'core/domain/notifications'
import { INotificationService } from 'core/services/notifications'
/**
* Firbase notification service
*
* @export
* @class NotificationService
* @implements {INotificationService}
*/
export class NotificationService implements INotificationService {
public addNotification: (notification: Notification)
=> Promise<void> = (notification: Notification) => {
return new Promise<void>((resolve,reject) => {
firebaseRef.child(`userNotifies/${notification.notifyRecieverUserId}`)
.push(notification)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public getNotifications: (userId: string, callback: (resultNotifications: {[notifyId: string]: Notification}) => void)
=> void = (userId,callback) => {
let notificationsRef = firebaseRef.child(`userNotifies/${userId}`)
notificationsRef.on('value', (snapshot: any) => {
let notifications: {[notifyId: string]: Notification} = snapshot.val() || {}
callback(notifications)
})
}
public deleteNotification: (notificationId: string, userId: string)
=> Promise < void > = (notificationId, userId) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`userNotifies/${userId}/${notificationId}`] = null
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public setSeenNotification: (notificationId: string, userId: string, notification: Notification)
=> Promise <void> = (notificationId, userId, notification) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`userNotifies/${userId}/${notificationId}`] = notification
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,101 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { Post } from 'core/domain/posts'
import { IPostService } from 'core/services/posts'
/**
* Firbase post service
*
* @export
* @class PostService
* @implements {IPostService}
*/
export class PostService implements IPostService {
public addPost: (userId: string, post: Post)
=> Promise<string> = (userId, post) => {
return new Promise<string>((resolve,reject) => {
let postRef: any = firebaseRef.child(`users/${userId}/posts`).push(post)
postRef.then(() => {
resolve(postRef.key)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public updatePost: (userId: string, postId: string, post: Post)
=> Promise<void> = (userId, postId, post) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/posts/${postId}`] = post
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public deletePost: (userId: string, postId: string)
=> Promise<void> = (userId, postId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`users/${userId}/posts/${postId}`] = null
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getPosts: (userId: string)
=> Promise<{ [postId: string]: Post }> = (userId) => {
return new Promise<{ [postId: string]: Post }>((resolve,reject) => {
let postsRef: any = firebaseRef.child(`users/${userId}/posts`)
postsRef.once('value').then((snapshot: any) => {
let posts: any = snapshot.val() || {}
let parsedPosts: { [postId: string]: Post } = {}
Object.keys(posts).forEach((postId) => {
parsedPosts[postId] = {
id: postId,
...posts[postId]
}
})
resolve(parsedPosts)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getPostById: (userId: string, postId: string)
=> Promise<Post> = (userId, postId) => {
return new Promise<Post>((resolve,reject) => {
let postsRef: any = firebaseRef.child(`users/${userId}/posts/${postId}`)
postsRef.once('value').then((snapshot: any) => {
let newPost = snapshot.val() || {}
let post: Post = {
id: postId,
...newPost
}
resolve(post)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { PostService } from './PostService'
export {
PostService
}

View File

@@ -1,109 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import moment from 'moment'
import { SocialError } from 'core/domain/common'
import { Profile, UserProvider } from 'core/domain/users'
import { IUserService } from 'core/services/users'
/**
* Firbase user service
*
* @export
* @class UserService
* @implements {IUserService}
*/
export class UserService implements IUserService {
public getUserProfile: (userId: string)
=> Promise<Profile> = (userId) => {
return new Promise<Profile>((resolve, reject) => {
let userProfileRef: any = firebaseRef.child(`users/${userId}/info`)
userProfileRef.once('value').then((snapshot: any) => {
let userProfile: Profile = snapshot.val() || {}
if (Object.keys(userProfile).length === 0 && userProfile.constructor === Object) {
this.getUserProviderData(userId).then((providerData: UserProvider) => {
const {avatar,fullName, email} = providerData
const userProfile = new Profile(avatar,fullName,'','', moment().unix(),email)
resolve(userProfile)
this.updateUserProfile(userId,userProfile)
})
} else {
resolve(userProfile)
}
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public updateUserProfile: (userId: string, profile: Profile)
=> Promise<void> = (userId, profile) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`users/${userId}/info`] = profile
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public getUsersProfile: (userId: string, page?: number, lastKey?: string)
=> Promise<{ [userId: string]: Profile }> = (userId, page, lastKey) => {
return new Promise<{ [userId: string]: Profile }>((resolve, reject) => {
let usersProfileRef: any
if (page) {
const numberOfItems = (page * 12) + 12
usersProfileRef = firebaseRef.child(`users`).orderByKey().startAt(lastKey!).limitToFirst(numberOfItems)
} else {
usersProfileRef = firebaseRef.child(`users`).orderByKey()
}
usersProfileRef.once('value').then((snapshot: any) => {
let usersProfile: any = snapshot.val() || {}
let parsedusersProfile: { [userId: string]: Profile } = {}
Object.keys(usersProfile).forEach((userKey) => {
if (userId !== userKey) {
let userInfo = usersProfile[userKey].info
parsedusersProfile[userKey] = {
avatar: userInfo.avatar,
email: userInfo.email,
fullName: userInfo.fullName,
banner: userInfo.banner,
tagLine: userInfo.tagLine
}
}
})
resolve(parsedusersProfile)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
private getUserProviderData = (userId: string) => {
return new Promise<UserProvider>((resolve,reject) => {
let userProviderRef: any = firebaseRef.child(`users/${userId}/providerInfo`)
userProviderRef.once('value').then((snapshot: any) => {
let userProviderRef: any = firebaseRef.child(`users/${userId}/info`)
let userProvider: UserProvider = snapshot.val() || {}
console.log('----------------userProfile')
console.log(userProvider)
console.log('-----------------------')
resolve(userProvider)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,5 +0,0 @@
import { UserService } from './UserService'
export {
UserService
}

View File

@@ -1,57 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { Vote } from 'core/domain/votes'
import { IVoteService } from 'core/services/votes'
/**
* Firbase vote service
*
* @export
* @class VoteService
* @implements {IVoteService}
*/
export class VoteService implements IVoteService {
public addVote: (vote: Vote)
=> Promise<string> = (vote) => {
return new Promise<string>((resolve,reject) => {
let voteRef = firebaseRef.child(`postVotes/${vote.postId}`)
.push(vote)
voteRef.then(() => {
resolve(voteRef.key!)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getVotes: ()
=> Promise<{ [postId: string]: { [voteId: string]: Vote } }> = () => {
return new Promise<{ [postId: string]: { [voteId: string]: Vote } }>((resolve,reject) => {
let votesRef: any = firebaseRef.child(`postVotes`)
votesRef.on('value',(snapshot: any) => {
let postVotes: {[postId: string]: {[voteId: string]: Vote}} = snapshot.val() || {}
resolve(postVotes)
})
})
}
public deleteVote: (voteId: string, postId: string)
=> Promise<void> = (voteId, postId) => {
return new Promise<void>((resolve,reject) => {
let updates: any = {}
updates[`postVotes/${postId}/${voteId}`] = null
firebaseRef.update(updates).then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -0,0 +1,49 @@
import { GraphService } from './services/graphs/GraphService'
import { IGraphService } from './services/graphs/IGraphService'
import { VoteService } from './services/votes/VoteService'
import { PostService } from './services/posts/PostService'
import { StorageService } from './services/files/StorageService'
import { CommonService } from './services/common/CommonService'
import { CommentService } from './services/comments/CommentService'
import { ICircleService } from 'core/services/circles/ICircleService'
import { Container } from 'inversify'
import { IUserService } from 'core/services/users'
import { SocialProviderTypes } from 'core/socialProviderTypes'
import { UserService } from './services/users/UserService'
import { IAuthorizeService } from 'core/services/authorize'
import { AuthorizeService } from './services/authorize/AuthorizeService'
import { CircleService } from './services/circles/CircleService'
import { ICommentService } from 'core/services/comments'
import { ICommonService } from 'core/services/common'
import { IStorageService } from 'core/services/files'
import { IImageGalleryService } from 'core/services/imageGallery'
import { INotificationService } from 'core/services/notifications'
import { IPostService } from 'core/services/posts'
import { IVoteService } from 'core/services/votes'
import { ImageGalleryService } from './services/imageGallery/ImageGalleryService'
import { NotificationService } from './services/notifications/NotificationService'
import { FirestoreClientTypes } from './firestoreClientTypes'
import { IUserTieService } from 'core/services/circles'
import { UserTieService } from './services/circles/UserTieService'
/**
* Register firestore client dependecies
* @param container DI container
*/
export const useFirestore = (container: Container) => {
container.bind<IAuthorizeService>(SocialProviderTypes.AuthorizeService).to(AuthorizeService)
container.bind<ICircleService>(SocialProviderTypes.CircleService).to(CircleService)
container.bind<ICommentService>(SocialProviderTypes.CommentService).to(CommentService)
container.bind<ICommonService>(SocialProviderTypes.CommonService).to(CommonService)
container.bind<IStorageService>(SocialProviderTypes.StorageService).to(StorageService)
container.bind<IImageGalleryService>(SocialProviderTypes.ImageGalleryService).to(ImageGalleryService)
container.bind<INotificationService>(SocialProviderTypes.NotificationService).to(NotificationService)
container.bind<IPostService>(SocialProviderTypes.PostService).to(PostService)
container.bind<IUserService>(SocialProviderTypes.UserService).to(UserService)
container.bind<IVoteService>(SocialProviderTypes.VoteService).to(VoteService)
container.bind<IGraphService>(FirestoreClientTypes.GraphService).to(GraphService)
container.bind<IUserTieService>(SocialProviderTypes.UserTieService).to(UserTieService)
}

View File

@@ -0,0 +1,7 @@
/**
* InversifyJS need to use the type as identifiers at runtime.
* We use symbols as identifiers but you can also use classes and or string literals.
*/
export const FirestoreClientTypes = {
GraphService: Symbol('GraphService')
}

View File

@@ -10,6 +10,7 @@ import { SocialError } from 'core/domain/common'
import { OAuthType } from 'core/domain/authorize/oauthType'
import moment from 'moment'
import { injectable } from 'inversify'
/**
* Firbase authorize service
*
@@ -17,6 +18,7 @@ import moment from 'moment'
* @class AuthorizeService
* @implements {IAuthorizeService}
*/
@injectable()
export class AuthorizeService implements IAuthorizeService {
/**
@@ -215,6 +217,8 @@ export class AuthorizeService implements IAuthorizeService {
return new Promise<RegisterUserResult>((resolve,reject) => {
db.doc(`userInfo/${userId}`).set(
{
id: userId,
state: 'active',
avatar,
fullName,
creationDate: moment().unix(),

View File

@@ -3,8 +3,9 @@ import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { ICircleService } from 'core/services/circles'
import { Circle, UserFollower } from 'core/domain/circles'
import { Circle, UserTie } from 'core/domain/circles'
import { User } from 'core/domain/users'
import { injectable } from 'inversify'
/**
* Firbase circle service
@@ -13,12 +14,13 @@ import { User } from 'core/domain/users'
* @class CircleService
* @implements {ICircleService}
*/
@injectable()
export class CircleService implements ICircleService {
public addCircle: (userId: string, circle: Circle)
=> Promise<string> = (userId, circle) => {
return new Promise<string>((resolve,reject) => {
let circleRef = db.doc(`users/${userId}`).collection(`circles`).add(circle)
let circleRef = db.doc(`users/${userId}`).collection(`circles`).add({...circle})
circleRef.then((result) => {
resolve(result.id as string)
})
@@ -27,43 +29,6 @@ export class CircleService implements ICircleService {
}
public addFollowingUser: (userId: string, circleId: string, userCircle: User, userFollower: UserFollower, userFollowingId: string)
=> Promise<void> = (userId, circleId, userCircle, userFollower, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const followerRef = db.doc(`users/${userId}/circles/${circleId}/users/${userFollowingId}`)
const followingRef = db.doc(`users/${userFollowingId}/circles/-Followers/users/${userId}`)
batch.update(followerRef, userCircle)
batch.update(followingRef, userFollower)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteFollowingUser: (userId: string, circleId: string, userFollowingId: string)
=> Promise<void> = (userId, circleId, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const followerRef = db.doc(`users/${userId}/circles/${circleId}/users/${userFollowingId}`)
const followingRef = db.doc(`users/${userFollowingId}/circles/-Followers/users/${userId}`)
batch.delete(followerRef)
batch.delete(followingRef)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public updateCircle: (userId: string, circleId: string, circle: Circle)
=> Promise<void> = (userId, circleId, circle) => {
return new Promise<void>((resolve,reject) => {
@@ -71,7 +36,7 @@ export class CircleService implements ICircleService {
const batch = db.batch()
const circleRef = db.doc(`users/${userId}/circles/${circleId}`)
batch.update(circleRef,circle)
batch.update(circleRef,{...circle})
batch.commit().then(() => {
resolve()
})

View File

@@ -0,0 +1,153 @@
// - Import react components
import { datumString } from 'aws-sdk/clients/athena'
import firebase, { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import moment from 'moment'
import { SocialError } from 'core/domain/common'
import { Profile, UserProvider, User } from 'core/domain/users'
import { IUserTieService } from 'core/services/circles'
import { inject, injectable } from 'inversify'
import { FirestoreClientTypes } from '../../firestoreClientTypes'
import { IGraphService } from '../graphs/IGraphService'
import { Graph } from 'core/domain/graphs'
import { UserTie } from 'core/domain/circles'
/**
* Firbase user service
*
* @export
* @class UserTieService
* @implements {IUserTieService}
*/
@injectable()
export class UserTieService implements IUserTieService {
constructor (
@inject(FirestoreClientTypes.GraphService) private _graphService: IGraphService
) {
}
/**
* Tie users
*/
public tieUseres: (userTieSenderInfo: UserTie, userTieReceiveInfo: UserTie, circleIds: string[])
=> Promise<void> = (userTieSenderInfo, userTieReceiveInfo, circleIds) => {
return new Promise<void>((resolve, reject) => {
this._graphService
.addGraph(
new Graph(
userTieSenderInfo.userId!,
'TIE',
userTieReceiveInfo.userId!,
{...userTieSenderInfo},
{...userTieReceiveInfo},
{creationDate: Date.now(), circleIds}
)
,'users'
).then((result) => {
resolve()
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/tieUseres :' + error.message)))
})
}
/**
* Remove users' tie
*/
public removeUsersTie: (firstUserId: string, secondUserId: string)
=> Promise<void> = (firstUserId, secondUserId) => {
return new Promise<void>((resolve, reject) => {
this.getUserTiesWithSeconUser(firstUserId,secondUserId).then((userTies) => {
if (userTies.length > 0) {
this._graphService.deleteGraphByNodeId(userTies[0].nodeId!).then(resolve)
}
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/removeUsersTie :' + error.message)))
})
}
/**
* Get user ties
*/
public getUserTies: (userId: string)
=> Promise<{[userId: string]: UserTie}> = (userId) => {
return new Promise<{[userId: string]: UserTie}>((resolve, reject) => {
this._graphService
.getGraphs(
'users',
userId,
'TIE')
.then((result) => {
let parsedData: {[userId: string]: UserTie} = {}
result.forEach((node) => {
const leftUserInfo: UserTie = node.LeftMetadata
const rightUserInfo: UserTie = node.rightMetadata
const metadata: {creationDate: number, circleIds: string[]} = node.graphMetadata
parsedData = {
...parsedData,
[rightUserInfo.userId!] : {
...node.rightMetadata,
circleIdList: metadata ? metadata.circleIds : []
}
}
})
resolve(parsedData)
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/getUserTies :' + error.message)))
})
}
/**
* Get the users who tied current user
*/
public getUserTieSender: (userId: string)
=> Promise<{[userId: string]: UserTie}> = (userId) => {
return new Promise<{[userId: string]: UserTie}>((resolve, reject) => {
this._graphService
.getGraphs(
'users',
null,
'TIE',
userId
)
.then((result) => {
let parsedData: {[userId: string]: UserTie} = {}
result.forEach((node) => {
const leftUserInfo: UserTie = node.LeftMetadata
const rightUserInfo: UserTie = node.rightMetadata
const metada: {creationDate: number, circleIds: string[]} = node.graphMetadata
parsedData = {
...parsedData,
[leftUserInfo.userId!] : {
...parsedData[leftUserInfo.userId!],
circleIdList: []
}
}
})
resolve(parsedData)
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/getUserTieSender :' + error.message)))
})
}
/**
* Get user ties with second user identifier
*/
private getUserTiesWithSeconUser: (userId: string, secondUserId: string)
=> Promise<Graph[]> = (userId, secondUserId) => {
return new Promise<Graph[]>((resolve, reject) => {
this._graphService
.getGraphs(
'users',
userId,
'TIE',
secondUserId
).then(resolve)
.catch(reject)
})
}
}

View File

@@ -5,6 +5,7 @@ import _ from 'lodash'
import { SocialError } from 'core/domain/common'
import { ICommentService } from 'core/services/comments'
import { Comment } from 'core/domain/comments'
import { injectable } from 'inversify'
/**
* Firbase comment service
@@ -13,42 +14,20 @@ import { Comment } from 'core/domain/comments'
* @class CommentService
* @implements {ICommentService}
*/
@injectable()
export class CommentService implements ICommentService {
/**
* Add comment
*
* @memberof CommentService
*/
public addComment: (comment: Comment)
=> Promise<string> = (comment) => {
return new Promise<string>((resolve,reject) => {
const postRef = db.doc(`posts/${comment.postId}`)
let commentRef = postRef.collection('comments')
let commentRef = db.collection('comments')
commentRef.add(comment).then((result) => {
resolve(result.id)
/**
* Add comment counter and three comments' slide preview
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const commentCount = postDoc.data().commentCounter + 1
transaction.update(postRef, { commentCounter: commentCount })
let comments = postDoc.data()
if (!comments) {
comments = {}
}
if (commentCount < 4) {
transaction.update(postRef, { comments: { ...comments, [result.id]: comment } })
} else {
let sortedObjects = comments
// Sort posts with creation date
sortedObjects.sort((a: any, b: any) => {
return parseInt(b.creationDate,10) - parseInt(a.creationDate,10)
})
const lastCommentId = Object.keys(sortedObjects)[2]
comments[lastCommentId] = {... comment}
transaction.update(postRef, { comments: { ...comments} })
}
}
})
})
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
@@ -56,9 +35,14 @@ export class CommentService implements ICommentService {
})
}
/**
* Get comments
*
* @memberof CommentService
*/
public getComments: (postId: string, callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void)
=> void = (postId, callback) => {
let commentsRef = db.doc(`posts/${postId}`).collection(`comments`)
let commentsRef = db.collection(`comments`).where('postId', '==', postId)
commentsRef.onSnapshot((snapshot) => {
let parsedData: {[postId: string]: {[commentId: string]: Comment}} = {[postId]: {}}
snapshot.forEach((result) => {
@@ -73,13 +57,18 @@ export class CommentService implements ICommentService {
})
}
/**
* Update comment
*
* @memberof CommentService
*/
public updateComment: (comment: Comment)
=> Promise<void> = (comment) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const commentRef = db.doc(`posts/${comment.postId}/comments/${comment.id}`)
const commentRef = db.collection(`comments`).doc(comment.id!)
batch.update(commentRef, comment)
batch.update(commentRef, {...comment})
batch.commit().then(() => {
resolve()
})
@@ -89,45 +78,25 @@ export class CommentService implements ICommentService {
})
}
public deleteComment: (commentId: string, postId: string)
=> Promise<void> = (commentId, postId) => {
/**
* Delete comment
*
* @memberof CommentService
*/
public deleteComment: (commentId: string)
=> Promise<void> = (commentId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const postRef = db.doc(`posts/${postId}`)
const commentRef = postRef.collection(`comments`).doc(commentId)
const commentCollectionRef = db.collection(`comments`)
const commentRef = commentCollectionRef.doc(commentId)
const batch = db.batch()
batch.delete(commentRef)
batch.commit().then(() => {
resolve()
/**
* Delete comment counter and comments' slide preview
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const commentCount = postDoc.data().commentCounter - 1
transaction.update(postRef, { commentCounter: commentCount })
if (commentCount > 3) {
let comments = postDoc.data().comments
if (!comments) {
comments = {}
}
let parsedComments = {}
Object.keys(postDoc.data().comments).map((id) => {
if (id !== commentId) {
_.merge(parsedComments, { [id]: { ...comments[id] } })
}
})
transaction.update(postRef, { comments: { ...parsedComments}})
}
}
})
})
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -3,6 +3,7 @@ import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICommonService } from 'core/services/common'
import { injectable } from 'inversify'
/**
* Firbase common service
@@ -11,6 +12,7 @@ import { ICommonService } from 'core/services/common'
* @class CommonService
* @implements {ICommonService}
*/
@injectable()
export class CommonService implements ICommonService {
}

Some files were not shown because too many files have changed in this diff Show More