├── apps
├── search_app
│ ├── .dockerignore
│ ├── src
│ │ └── main
│ │ │ └── resources
│ │ │ ├── config.properties
│ │ │ └── commandmap.properties
│ ├── Dockerfile
│ └── docker-compose.yml
├── thread_app
│ ├── .dockerignore
│ ├── src
│ │ ├── main
│ │ │ ├── resources
│ │ │ │ ├── cacheThreshold.properties
│ │ │ │ ├── configurations.properties
│ │ │ │ ├── config.properties
│ │ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── thread
│ │ │ │ └── commands
│ │ │ │ └── GetFollowedThreads.java
│ │ └── test
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── thread
│ │ │ └── ThreadAppTest.java
│ ├── Dockerfile
│ └── docker-compose.yml
├── user_app
│ ├── .dockerignore
│ ├── src
│ │ └── main
│ │ │ ├── resources
│ │ │ ├── config.properties
│ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── user
│ │ │ ├── commands
│ │ │ ├── ViewAnotherProfile.java
│ │ │ ├── ViewMyProfile.java
│ │ │ ├── Login.java
│ │ │ ├── UpdatePassword.java
│ │ │ ├── DeleteProfilePhoto.java
│ │ │ └── UpdateProfilePhoto.java
│ │ │ └── UserApp.java
│ ├── Dockerfile
│ └── docker-compose.yml
├── notification_app
│ ├── .dockerignore
│ ├── src
│ │ ├── main
│ │ │ ├── resources
│ │ │ │ ├── config.properties
│ │ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── notification
│ │ │ │ ├── NotificationSendingFailedException.java
│ │ │ │ ├── GoogleCredentialsLoadingFailedException.java
│ │ │ │ ├── NotificationApp.java
│ │ │ │ ├── FirebaseInitializer.java
│ │ │ │ └── commands
│ │ │ │ └── GetNotifications.java
│ │ └── test
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── notification
│ │ │ └── commands
│ │ │ └── SendNotificationTest.java
│ ├── .gitignore
│ ├── docker-compose.yml
│ └── Dockerfile
├── recommendation_app
│ ├── .dockerignore
│ ├── src
│ │ └── main
│ │ │ ├── resources
│ │ │ ├── config.properties
│ │ │ ├── config.development.json
│ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── recommendation
│ │ │ └── commands
│ │ │ ├── GetPopularThreads.java
│ │ │ └── GetPopularSubThreads.java
│ ├── Dockerfile
│ └── docker-compose.yml
├── subthread_app
│ ├── .dockerignore
│ ├── src
│ │ ├── main
│ │ │ ├── resources
│ │ │ │ ├── configurations.properties
│ │ │ │ ├── config.properties
│ │ │ │ ├── cacheThreshold.properties
│ │ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── subthread
│ │ │ │ └── commands
│ │ │ │ ├── GetComment.java
│ │ │ │ ├── GetSubThreads.java
│ │ │ │ ├── GetMyComments.java
│ │ │ │ ├── GetMyDislikedComments.java
│ │ │ │ ├── GetMySubThreads.java
│ │ │ │ ├── GetMyLikedComments.java
│ │ │ │ ├── GetMyDislikedSubThreads.java
│ │ │ │ ├── GetMyLikedSubThreads.java
│ │ │ │ └── ModeratorSeeReports.java
│ │ └── test
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── subthread
│ │ │ └── SubThreadAppTest.java
│ ├── Dockerfile
│ └── docker-compose.yml
├── chat_app
│ ├── chat_server
│ │ ├── .dockerignore
│ │ ├── src
│ │ │ ├── test
│ │ │ │ └── java
│ │ │ │ │ └── org
│ │ │ │ │ └── sab
│ │ │ │ │ └── chat
│ │ │ │ │ └── server
│ │ │ │ │ └── AppTest.java
│ │ │ └── main
│ │ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── chat
│ │ │ │ └── server
│ │ │ │ ├── handlers
│ │ │ │ ├── ResponseHandler.java
│ │ │ │ ├── TextWebSocketFrameHandler.java
│ │ │ │ └── QueueHandler.java
│ │ │ │ ├── routers
│ │ │ │ ├── RouterBuilder.java
│ │ │ │ ├── GetGroupMessagesRouter.java
│ │ │ │ ├── GetChatsRouter.java
│ │ │ │ ├── GetDirectMessagesRouter.java
│ │ │ │ ├── CreateGroupMessageRouter.java
│ │ │ │ ├── CreateDirectMessageRouter.java
│ │ │ │ ├── AddMemberRouter.java
│ │ │ │ ├── RemoveMemberRouter.java
│ │ │ │ ├── CreateGroupChatRouter.java
│ │ │ │ ├── LeaveGroupRouter.java
│ │ │ │ └── CreateDirectChatRouter.java
│ │ │ │ ├── ChatServerInitializer.java
│ │ │ │ └── ChatServer.java
│ │ └── Dockerfile
│ ├── chat_storage
│ │ ├── .dockerignore
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── resources
│ │ │ │ │ ├── config.properties
│ │ │ │ │ └── commandmap.properties
│ │ │ │ └── java
│ │ │ │ │ └── org
│ │ │ │ │ └── sab
│ │ │ │ │ └── chat
│ │ │ │ │ └── storage
│ │ │ │ │ ├── ChatStorageApp.java
│ │ │ │ │ ├── exceptions
│ │ │ │ │ └── InvalidInputException.java
│ │ │ │ │ ├── config
│ │ │ │ │ └── KeyspaceInitializer.java
│ │ │ │ │ ├── tables
│ │ │ │ │ └── TableUtils.java
│ │ │ │ │ └── models
│ │ │ │ │ └── DirectChat.java
│ │ │ └── test
│ │ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── chat
│ │ │ │ └── storage
│ │ │ │ ├── AppTest.java
│ │ │ │ └── config
│ │ │ │ └── CassandraConnectorTest.java
│ │ └── Dockerfile
│ ├── README.md
│ └── docker-compose.yml
├── user_to_user_actions_app
│ ├── .dockerignore
│ ├── src
│ │ └── main
│ │ │ ├── resources
│ │ │ ├── config.properties
│ │ │ └── commandmap.properties
│ │ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── useractions
│ │ │ └── UserToUserActionsApp.java
│ ├── Dockerfile
│ └── docker-compose.yml
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
├── example_app
│ └── src
│ │ ├── main
│ │ ├── resources
│ │ │ ├── config.properties
│ │ │ ├── ImprovedHello
│ │ │ ├── MorningWorld
│ │ │ └── commandmap.properties
│ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── demo
│ │ │ ├── commands
│ │ │ ├── GoodByeWorld.java
│ │ │ ├── HelloWorld.java
│ │ │ ├── CommandWithoutAuth.java
│ │ │ ├── CommandNeedingAuth.java
│ │ │ ├── CommandNeedingClaims.java
│ │ │ └── HelloArango.java
│ │ │ └── ExampleApp.java
│ │ └── test
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── demo
│ │ ├── CommandWithoutAuthTest.java
│ │ ├── CommandNeedingAuthTest.java
│ │ └── CommandNeedingClaimsTest.java
└── controller
│ └── src
│ ├── main
│ ├── resources
│ │ ├── apps-ports.properties
│ │ └── apps-ips.properties
│ └── java
│ │ └── org
│ │ └── sab
│ │ └── controller
│ │ ├── ControllerClientHandler.java
│ │ └── ControllerClient.java
│ └── test
│ └── java
│ └── org
│ └── sab
│ └── controller
│ └── ControllerTest.java
├── libs
├── netty
│ ├── .dockerignore
│ ├── Dockerfile
│ └── src
│ │ └── main
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── netty
│ │ ├── middleware
│ │ └── QueueHandler.java
│ │ └── ServerInitializer.java
├── arango
│ └── src
│ │ └── main
│ │ └── resources
│ │ └── config.properties
├── postgres
│ └── src
│ │ ├── main
│ │ └── resources
│ │ │ ├── sql
│ │ │ ├── DropUsersTable.sql
│ │ │ ├── CreateUsersTable.sql
│ │ │ └── DropUserProcedures.sql
│ │ │ └── config.example.json
│ │ └── test
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── postgres
│ │ └── PostgresConnectionTest.java
├── redis
│ └── src
│ │ └── main
│ │ └── resources
│ │ └── config.properties
├── utilities
│ └── src
│ │ ├── main
│ │ ├── resources
│ │ │ ├── CharlieFlying
│ │ │ ├── CharlieHopping
│ │ │ ├── SampleImage.png
│ │ │ └── org
│ │ │ │ └── sab
│ │ │ │ └── classes
│ │ │ │ └── Alice.class
│ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ ├── functions
│ │ │ ├── TriFunction.java
│ │ │ └── Utilities.java
│ │ │ ├── classes
│ │ │ ├── Bob.java
│ │ │ ├── ClassRegistry.java
│ │ │ ├── Reader.java
│ │ │ └── ByteClassLoader.java
│ │ │ ├── databases
│ │ │ ├── PoolDoesNotExistException.java
│ │ │ └── PooledDatabaseClient.java
│ │ │ ├── validation
│ │ │ ├── exceptions
│ │ │ │ └── EnvironmentVariableNotLoaded.java
│ │ │ ├── Schema.java
│ │ │ └── Attribute.java
│ │ │ ├── auth
│ │ │ └── Auth.java
│ │ │ ├── futures
│ │ │ └── FutureUtil.java
│ │ │ ├── reflection
│ │ │ └── ReflectionUtils.java
│ │ │ ├── minio
│ │ │ └── FileSimulation.java
│ │ │ ├── environmentvariables
│ │ │ └── EnvVariablesUtils.java
│ │ │ ├── io
│ │ │ └── IoUtils.java
│ │ │ ├── strings
│ │ │ └── StringManipulation.java
│ │ │ ├── tests
│ │ │ └── TestsUtils.java
│ │ │ └── HttpServerUtilities
│ │ │ └── HttpClient.java
│ │ └── test
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ ├── functions
│ │ └── TriFunctionTest.java
│ │ ├── environmentvariables
│ │ └── EnvVariablesUtilsTest.java
│ │ └── futures
│ │ └── FutureUtilTest.java
├── service
│ └── src
│ │ ├── main
│ │ ├── resources
│ │ │ └── dbmap.properties
│ │ └── java
│ │ │ └── org
│ │ │ └── sab
│ │ │ └── service
│ │ │ ├── validation
│ │ │ ├── HTTPMethod.java
│ │ │ └── RequestVerificationException.java
│ │ │ ├── Command.java
│ │ │ ├── Service.java
│ │ │ ├── ServiceConstants.java
│ │ │ ├── Responder.java
│ │ │ ├── managers
│ │ │ ├── ThreadPoolManager.java
│ │ │ └── QueueManager.java
│ │ │ ├── databases
│ │ │ └── DBConfig.java
│ │ │ └── controllerbackdoor
│ │ │ └── BackdoorServerHandler.java
│ │ └── test
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── service
│ │ └── ServiceTest.java
├── couchbase
│ ├── Dockerfile
│ ├── configure-server.sh
│ └── src
│ │ └── main
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── couchbase
│ │ └── config
│ │ └── buckets.json
├── models
│ └── src
│ │ └── main
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ └── models
│ │ ├── report
│ │ ├── TypeOfReport.java
│ │ └── SubThreadReportAttributes.java
│ │ ├── EdgeCollectionsAttributes.java
│ │ ├── AuthenticationAttributes.java
│ │ ├── NotificationAttributes.java
│ │ ├── RequestAttributes.java
│ │ ├── CouchbaseBuckets.java
│ │ ├── ThreadAttributes.java
│ │ ├── SubThreadAttributes.java
│ │ ├── CommentAttributes.java
│ │ └── CollectionNames.java
├── rabbitmq
│ └── src
│ │ └── main
│ │ └── java
│ │ └── org
│ │ └── sab
│ │ ├── innerAppComm
│ │ └── RequestType.java
│ │ └── rabbitmq
│ │ ├── RPCBase.java
│ │ ├── SingleBasicChannel.java
│ │ ├── RPCClient.java
│ │ └── RPCServer.java
└── example_util
│ └── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── sab
│ │ └── strings
│ │ └── ExampleUtil.java
│ └── test
│ └── java
│ └── org
│ └── sab
│ └── strings
│ └── ExampleUtilTest.java
├── notification-client
├── .vscode
│ └── settings.json
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── firebase-messaging-sw.js
│ └── index.html
├── src
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── reportWebVitals.js
│ ├── index.js
│ └── App.css
├── .gitignore
└── package.json
├── cli
├── contexts
│ ├── app-context.js
│ └── chat-context.js
├── .editorconfig
├── .prettierrc
├── components
│ ├── loading-spinner.js
│ ├── welcome-header.js
│ ├── messages-list.js
│ ├── counter.js
│ ├── message.js
│ ├── create-group-chat.js
│ ├── create-direct-chat.js
│ ├── add-group-member.js
│ ├── remove-group-member.js
│ └── chat-view.js
├── test.js
├── readme.md
├── flows
│ ├── welcome-flow.js
│ └── chat-flow.js
├── cli.js
├── package.json
└── app.js
├── docker-compose.yml
├── .idea
├── vcs.xml
└── sqldialects.xml
├── README.md
└── pom.xml
/apps/search_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
--------------------------------------------------------------------------------
/libs/netty/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/thread_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/user_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/notification_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/recommendation_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
--------------------------------------------------------------------------------
/apps/subthread_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 |
--------------------------------------------------------------------------------
/apps/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx
2 | COPY nginx.conf /etc/nginx/nginx.conf
--------------------------------------------------------------------------------
/apps/notification_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 |
--------------------------------------------------------------------------------
/libs/arango/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | NUMBER_OF_CONNECTIONS=10
2 |
--------------------------------------------------------------------------------
/apps/thread_app/src/main/resources/cacheThreshold.properties:
--------------------------------------------------------------------------------
1 | THREAD_FOLLOWERS = 1000
--------------------------------------------------------------------------------
/apps/thread_app/src/main/resources/configurations.properties:
--------------------------------------------------------------------------------
1 | THREAD_RAM_QUOTA=100
--------------------------------------------------------------------------------
/notification-client/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/apps/subthread_app/src/main/resources/configurations.properties:
--------------------------------------------------------------------------------
1 | COMMENT_RAM_QUOTA=100
2 | SUBTHREAD_RAM_QUOTA=100
3 |
--------------------------------------------------------------------------------
/notification-client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/apps/search_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/apps/thread_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/apps/user_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/libs/postgres/src/main/resources/sql/DropUsersTable.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE users CASCADE;
2 | DROP TABLE deleted_users CASCADE;
3 |
--------------------------------------------------------------------------------
/apps/subthread_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/notification-client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/notification-client/public/favicon.ico
--------------------------------------------------------------------------------
/notification-client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/notification-client/public/logo192.png
--------------------------------------------------------------------------------
/notification-client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/notification-client/public/logo512.png
--------------------------------------------------------------------------------
/apps/recommendation_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/libs/redis/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | OPERATION_TIMEOUT_MINUTES=1
2 | DATABASE_NUMBER=0
3 | NUMBER_OF_CONNECTIONS=10
4 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.arango.Arango-10
3 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/resources/CharlieFlying:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/libs/utilities/src/main/resources/CharlieFlying
--------------------------------------------------------------------------------
/apps/example_app/src/main/resources/ImprovedHello:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/apps/example_app/src/main/resources/ImprovedHello
--------------------------------------------------------------------------------
/apps/example_app/src/main/resources/MorningWorld:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/apps/example_app/src/main/resources/MorningWorld
--------------------------------------------------------------------------------
/libs/service/src/main/resources/dbmap.properties:
--------------------------------------------------------------------------------
1 | ARANGO = org.sab.arango.Arango
2 | CASSANDRA = org.sab.chat.storage.config.CassandraConnector
3 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/resources/CharlieHopping:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/libs/utilities/src/main/resources/CharlieHopping
--------------------------------------------------------------------------------
/libs/utilities/src/main/resources/SampleImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/libs/utilities/src/main/resources/SampleImage.png
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/resources/config.properties:
--------------------------------------------------------------------------------
1 | threadsCount=11
2 | requiredDatabases = org.sab.chat.storage.config.CassandraConnector-10
3 |
--------------------------------------------------------------------------------
/apps/notification_app/.gitignore:
--------------------------------------------------------------------------------
1 | # Firebase SDK Private Key
2 | src/main/resources/service-account-file.json
3 |
4 | # Firebase Config
5 | firebase-config.json
--------------------------------------------------------------------------------
/apps/subthread_app/src/main/resources/cacheThreshold.properties:
--------------------------------------------------------------------------------
1 | SUBTHREAD_LIKES=1000
2 | SUBTHREAD_DISLIKES=1000
3 | COMMENT_LIKES=1000
4 | COMMENT_DISLIKES=1000
--------------------------------------------------------------------------------
/cli/contexts/app-context.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const React = require('react')
3 |
4 | const AppContext = React.createContext()
5 |
6 | module.exports = AppContext
7 |
--------------------------------------------------------------------------------
/libs/couchbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM couchbase
2 | COPY libs/couchbase/configure-server.sh /opt/couchbase/configure-server.sh
3 | CMD ["/opt/couchbase/configure-server.sh"]
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/report/TypeOfReport.java:
--------------------------------------------------------------------------------
1 | package org.sab.models.report;
2 |
3 | public enum TypeOfReport {
4 | SUBTHREAD_REPORT;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/search_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | SEARCH_SUBTHREAD=org.sab.search.commands.SearchSubThread
2 | SEARCH_THREAD=org.sab.search.commands.SearchThread
3 |
--------------------------------------------------------------------------------
/cli/contexts/chat-context.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const React = require('react')
3 |
4 | const ChatContext = React.createContext()
5 |
6 | module.exports = ChatContext
7 |
--------------------------------------------------------------------------------
/libs/rabbitmq/src/main/java/org/sab/innerAppComm/RequestType.java:
--------------------------------------------------------------------------------
1 | package org.sab.innerAppComm;
2 |
3 | public enum RequestType {
4 | GET, PUT, POST, DELETE;
5 | }
6 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/resources/org/sab/classes/Alice.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Moe98/reddit/HEAD/libs/utilities/src/main/resources/org/sab/classes/Alice.class
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.2"
2 | services:
3 | rabbitmq:
4 | image: rabbitmq:3-management-alpine
5 | ports:
6 | - 5672:5672
7 | - 15672:15672
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/functions/TriFunction.java:
--------------------------------------------------------------------------------
1 | package org.sab.functions;
2 |
3 | @FunctionalInterface
4 | public interface TriFunction {
5 | S apply(T a, U b);
6 | }
7 |
--------------------------------------------------------------------------------
/apps/controller/src/main/resources/apps-ports.properties:
--------------------------------------------------------------------------------
1 | chat=4001
2 | example=4002
3 | notification=4003
4 | recommendation=4004
5 | search=4005
6 | subthread=4006
7 | thread=4007
8 | user=4008
9 | useraction=4009
--------------------------------------------------------------------------------
/apps/example_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | HELLO_WORLD = org.sab.demo.commands.HelloWorld
2 | GOOD_BYE_WORLD = org.sab.demo.commands.GoodByeWorld
3 | HELLO_ARANGO = org.sab.demo.commands.HelloArango
--------------------------------------------------------------------------------
/.idea/sqldialects.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/classes/Bob.java:
--------------------------------------------------------------------------------
1 | package org.sab.classes;
2 |
3 | @SuppressWarnings("unused")
4 | public class Bob {
5 |
6 | public String talk() {
7 | return "Hi";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/cli/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.yml]
11 | indent_style = space
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/apps/recommendation_app/src/main/resources/config.development.json:
--------------------------------------------------------------------------------
1 | {
2 | "COUCHBASE_HOST": "127.0.0.1",
3 | "COUCHBASE_USERNAME": "",
4 | "COUCHBASE_PASSWORD": "",
5 | "ARANGO_USER": "",
6 | "ARANGO_PASSWORD": "",
7 | "ARANGO_DB": "Reddit"
8 | }
--------------------------------------------------------------------------------
/cli/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "jsxSingleQuote": true,
4 | "semi": false,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "jsxBracketSameLine": false,
8 | "arrowParens": "always",
9 | "trailingComma": "none"
10 | }
--------------------------------------------------------------------------------
/apps/controller/src/main/resources/apps-ips.properties:
--------------------------------------------------------------------------------
1 | chat_1=127.0.0.1
2 | example_1=127.0.0.1
3 | notification_1=127.0.0.1
4 | recommendation_1=127.0.0.1
5 | search_1=127.0.0.1
6 | subthread_1=127.0.0.1
7 | thread_1=127.0.0.1
8 | user_1=127.0.0.1
9 | useraction_1=127.0.0.1
--------------------------------------------------------------------------------
/apps/notification_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | SEND_NOTIFICATION=org.sab.notification.commands.SendNotification
2 | REGISTER_DEVICE_TOKEN=org.sab.notification.commands.RegisterDeviceToken
3 | GET_NOTIFICATIONS=org.sab.notification.commands.GetNotifications
4 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/databases/PoolDoesNotExistException.java:
--------------------------------------------------------------------------------
1 | package org.sab.databases;
2 |
3 | public class PoolDoesNotExistException extends Exception{
4 |
5 | public PoolDoesNotExistException(String message) {
6 | super(message);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/notification-client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/validation/HTTPMethod.java:
--------------------------------------------------------------------------------
1 | package org.sab.service.validation;
2 |
3 | public enum HTTPMethod {
4 | GET, PUT, POST, DELETE;
5 |
6 | public boolean equals(String methodType) {
7 | return toString().equals(methodType);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/notification-client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/libs/postgres/src/main/resources/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "POSTGRES_HOST":"",
3 | "POSTGRES_PORT":"",
4 | "POSTGRES_DB":"",
5 | "POSTGRES_USER":"",
6 | "POSTGRES_PASSWORD":"",
7 |
8 | "Cloudinary Keys":"",
9 | "cloud_name":"",
10 | "api_key":"",
11 | "api_secret":""
12 | }
13 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/validation/RequestVerificationException.java:
--------------------------------------------------------------------------------
1 | package org.sab.service.validation;
2 |
3 | public class RequestVerificationException extends Exception {
4 |
5 | public RequestVerificationException(String message) {
6 | super(message);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/apps/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | http {
2 | upstream servers {
3 | least_conn;
4 | server 127.0.0.1:8081;
5 | server 127.0.0.1:8082;
6 | }
7 |
8 | server {
9 | listen 8080;
10 | location / {
11 | proxy_pass http://servers/;
12 | }
13 | }
14 | }
15 |
16 | events { }
17 |
18 |
--------------------------------------------------------------------------------
/apps/notification_app/src/main/java/org/sab/notification/NotificationSendingFailedException.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification;
2 |
3 | public class NotificationSendingFailedException extends Exception {
4 | public NotificationSendingFailedException(String message, Throwable cause) {
5 | super(message, cause);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/test/java/org/sab/chat/server/AppTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import org.junit.Test;
6 |
7 | public class AppTest {
8 |
9 | @Test
10 | public void shouldAnswerWithTrue() {
11 | assertTrue( true );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/test/java/org/sab/chat/storage/AppTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import org.junit.Test;
6 |
7 | public class AppTest {
8 | @Test
9 | public void shouldAnswerWithTrue() {
10 | assertTrue( true );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/controller/src/test/java/org/sab/controller/ControllerTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.controller;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertTrue;
6 |
7 |
8 | public class ControllerTest {
9 |
10 | @Test
11 | public void shouldAnswerWithTrue() {
12 | assertTrue(true);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/apps/notification_app/src/main/java/org/sab/notification/GoogleCredentialsLoadingFailedException.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification;
2 |
3 | public class GoogleCredentialsLoadingFailedException extends Exception {
4 |
5 | public GoogleCredentialsLoadingFailedException(String message, Throwable cause) {
6 | super(message, cause);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | BLOCK_USER=org.sab.useractions.commands.BlockUser
2 | FOLLOW_USER=org.sab.useractions.commands.FollowUser
3 | GET_BLOCKED_USERS=org.sab.useractions.commands.GetBlockedUsers
4 | GET_FOLLOWED_USERS=org.sab.useractions.commands.GetFollowedUsers
5 | GET_MY_FOLLOWERS=org.sab.useractions.commands.GetMyFollowers
--------------------------------------------------------------------------------
/libs/example_util/src/main/java/org/sab/strings/ExampleUtil.java:
--------------------------------------------------------------------------------
1 | package org.sab.strings;
2 |
3 | import java.util.List;
4 | import java.util.stream.Collectors;
5 |
6 | public class ExampleUtil {
7 |
8 | public static List toCharList(String s) {
9 | return s.chars().mapToObj(c -> (char) c).collect(Collectors.toList());
10 | }
11 |
12 | }
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/GoodByeWorld.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import org.json.JSONObject;
4 | import org.sab.service.Command;
5 |
6 | public class GoodByeWorld extends Command {
7 |
8 | @Override
9 | public String execute(JSONObject request) {
10 | return "{\"msg\":\"GoodBye World\"}";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/validation/exceptions/EnvironmentVariableNotLoaded.java:
--------------------------------------------------------------------------------
1 | package org.sab.validation.exceptions;
2 |
3 | public class EnvironmentVariableNotLoaded extends Exception {
4 | public EnvironmentVariableNotLoaded(String envVariableName) {
5 | super(String.format("The %s environment variable was not loaded", envVariableName));
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/EdgeCollectionsAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum EdgeCollectionsAttributes {
4 | USER_FOLLOW_THREAD_DATE("Date");
5 |
6 | private final String db;
7 |
8 | EdgeCollectionsAttributes(String db) {
9 | this.db = db;
10 | }
11 |
12 | public String getDb() {
13 | return db;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/cli/components/loading-spinner.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text } = require('ink')
3 | const { default: Spinner } = require('ink-spinner')
4 |
5 | const LoadingSpinner = ({ loadingText = 'Loading' }) => (
6 |
7 |
8 |
9 |
10 | {' ' + loadingText}
11 |
12 | )
13 |
14 | module.exports = LoadingSpinner
15 |
--------------------------------------------------------------------------------
/cli/components/welcome-header.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const Gradient = require('ink-gradient')
3 | const BigText = require('ink-big-text')
4 |
5 | const WelcomeHeader = ({ rainbow, text }) => {
6 | return rainbow ? (
7 |
8 |
9 |
10 | ) : (
11 |
12 | )
13 | }
14 |
15 | module.exports = WelcomeHeader
16 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/java/org/sab/chat/storage/ChatStorageApp.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage;
2 |
3 | import org.sab.service.Service;
4 |
5 | public class ChatStorageApp extends Service {
6 |
7 | @Override
8 | public String getAppUriName() {
9 | return "CHAT";
10 | }
11 |
12 | public static void main(String[] args){
13 | new ChatStorageApp().start();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/libs/service/src/test/java/org/sab/service/ServiceTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.service;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import org.junit.Test;
6 |
7 | /**
8 | * Unit test for simple App.
9 | */
10 | public class ServiceTest {
11 | /**
12 | * Rigorous Test :-)
13 | */
14 | @Test
15 | public void shouldAnswerWithTrue() {
16 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/thread_app/src/test/java/org/sab/thread/ThreadAppTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.thread;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertTrue;
6 |
7 | /**
8 | * Unit test for simple App.
9 | */
10 | public class ThreadAppTest {
11 | /**
12 | * Rigorous Test :-)
13 | */
14 | @Test
15 | public void shouldAnswerWithTrue() {
16 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/ExampleApp.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo;
2 |
3 | import org.sab.service.Service;
4 |
5 | public class ExampleApp extends Service {
6 |
7 | public static void main(String[] args) {
8 | ExampleApp a = new ExampleApp();
9 | a.start();
10 | }
11 |
12 | @Override
13 | public String getAppUriName() {
14 | return "EXAMPLE";
15 | }
16 |
17 |
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/apps/subthread_app/src/test/java/org/sab/subthread/SubThreadAppTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.subthread;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertTrue;
6 |
7 | /**
8 | * Unit test for simple App.
9 | */
10 | public class SubThreadAppTest {
11 | /**
12 | * Rigorous Test :-)
13 | */
14 | @Test
15 | public void shouldAnswerWithTrue() {
16 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/notification-client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/notification-client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/notification-client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/libs/postgres/src/main/resources/sql/CreateUsersTable.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS users
2 | (
3 | username character varying(255) PRIMARY KEY,
4 | email character varying(500) UNIQUE,
5 | password character varying(255),
6 | birthdate date,
7 | photo_url text,
8 | user_id character varying(50) UNIQUE
9 | );
10 |
11 |
12 | CREATE TABLE IF NOT EXISTS deleted_users
13 | (
14 | username character varying(255) PRIMARY KEY
15 | );
16 |
--------------------------------------------------------------------------------
/cli/components/messages-list.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Box } = require('ink')
3 |
4 | const importJsx = require('import-jsx')
5 |
6 | const Message = importJsx('./message')
7 |
8 | const MessagesList = ({ messages }) => {
9 | return (
10 |
11 | {messages.map((message) => (
12 |
13 | ))}
14 |
15 | )
16 | }
17 |
18 | module.exports = MessagesList
19 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/AuthenticationAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum AuthenticationAttributes {
4 | AUTHENTICATION_PARAMS("authenticationParams"),
5 | IS_AUTHENTICATED("isAuthenticated");
6 | ;
7 | private final String value;
8 |
9 | AuthenticationAttributes(String value) {
10 | this.value = value;
11 | }
12 |
13 | public String getValue() {
14 | return value;
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/apps/user_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | SIGN_UP=org.sab.user.commands.SignUp
2 | UPDATE_PROFILE_PHOTO=org.sab.user.commands.UpdateProfilePhoto
3 | DELETE_PROFILE_PHOTO=org.sab.user.commands.DeleteProfilePhoto
4 | DELETE_ACCOUNT=org.sab.user.commands.DeleteAccount
5 | UPDATE_PASSWORD=org.sab.user.commands.UpdatePassword
6 | VIEW_MY_PROFILE=org.sab.user.commands.ViewMyProfile
7 | VIEW_ANOTHER_PROFILE=org.sab.user.commands.ViewAnotherProfile
8 | LOGIN=org.sab.user.commands.Login
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/NotificationAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum NotificationAttributes {
4 |
5 | USERS_LIST("usersList"),
6 | TITLE("title"),
7 | NOTIFICATION_BODY("notificationBody");
8 |
9 |
10 | private final String value;
11 |
12 | NotificationAttributes(String value) {
13 | this.value = value;
14 | }
15 |
16 | public String getValue() {
17 | return value;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/libs/example_util/src/test/java/org/sab/strings/ExampleUtilTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.strings;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.List;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | public class ExampleUtilTest {
10 |
11 | @Test
12 | public void shouldDivideHelloIntoItsLetters() {
13 | final List chars = ExampleUtil.toCharList("Hello");
14 | assertEquals(chars, List.of('H', 'e', 'l', 'l', 'o'));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cli/test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import chalk from 'chalk'
3 | import test from 'ava'
4 | import { render } from 'ink-testing-library'
5 | import App from './ui'
6 |
7 | test('greet unknown user', (t) => {
8 | const { lastFrame } = render()
9 |
10 | t.is(lastFrame(), chalk`Hello, {green Stranger}`)
11 | })
12 |
13 | test('greet user with a name', (t) => {
14 | const { lastFrame } = render()
15 |
16 | t.is(lastFrame(), chalk`Hello, {green Jane}`)
17 | })
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reddit
2 |
3 | A scalable Reddit replica that is able to handle ~2,300 concurrent users, each performing 10 requests per second for 10 minutes straight, with a mean response time of ~5 seconds.
4 |
5 | ## Technologies & Tools
6 |
7 | ### Server
8 |
9 | - Netty
10 | - RabbitMQ
11 |
12 | ### Load Balancing
13 |
14 | - Nginx
15 | - Minikube
16 |
17 | ### Databases
18 |
19 | - PostgreSQL
20 | - ArangoDB
21 | - Cassandra
22 | - Firebase
23 |
24 | ### Cache
25 |
26 | - Couchbase
27 | - Redis **Lettuce**
28 |
--------------------------------------------------------------------------------
/apps/notification_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - "15672:15672"
7 | - "5762:5762"
8 | notificiation_app:
9 | build:
10 | context: "../../"
11 | dockerfile: "apps/notification_app/Dockerfile"
12 | restart: on-failure
13 | ports:
14 | - "4003:4003"
15 | env_file:
16 | - "../../.env"
17 | depends_on:
18 | - "rabbitmq"
19 | networks:
20 | default:
21 | name: notification_nw
22 |
--------------------------------------------------------------------------------
/cli/components/counter.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text } = require('ink')
3 |
4 | const { useState, useEffect } = React
5 |
6 | const Counter = () => {
7 | const [counter, setCounter] = useState(0)
8 |
9 | useEffect(() => {
10 | const timer = setInterval(() => {
11 | setCounter((previousCounter) => previousCounter + 1)
12 | }, 100)
13 |
14 | return () => {
15 | clearInterval(timer)
16 | }
17 | }, [])
18 |
19 | return x{counter}
20 | }
21 |
22 | module.exports = Counter
23 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/RequestAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum RequestAttributes {
4 | BODY("body"),
5 | METHOD_TYPE("methodType"),
6 | AUTHENTICATION_PARAMS("authenticationParams"),
7 | FUNCTION_NAME("functionName"),
8 | URI_PARAMS("uriParams");
9 |
10 | private final String value;
11 |
12 | RequestAttributes(String value) {
13 | this.value = value;
14 | }
15 |
16 | public String getValue() {
17 | return value;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import org.json.JSONObject;
4 | import org.sab.service.Command;
5 |
6 |
7 |
8 | public class HelloWorld extends Command {
9 |
10 | @Override
11 | public String execute(JSONObject request) {
12 | // Return SUCCESS
13 | return "{\"msg\":\"Hello World\", \"statusCode\": 200}";
14 | // Return ERROR Ex: 400 for bad request
15 | // return "{\"msg\":\"Hello World\", \"statusCode\": 400}";
16 | }
17 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.sab
6 | reddit
7 | 1.0-SNAPSHOT
8 |
9 | libs
10 | apps
11 |
12 | pom
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/thread_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | ASSIGN_THREAD_MODERATOR=org.sab.thread.commands.AssignThreadModerator
2 | BOOKMARK_THREAD=org.sab.thread.commands.BookmarkThread
3 | CREATE_THREAD=org.sab.thread.commands.CreateThread
4 | DELETE_THREAD=org.sab.thread.commands.DeleteThread
5 | FOLLOW_THREAD=org.sab.thread.commands.FollowThread
6 | GET_FOLLOWED_THREADS=org.sab.thread.commands.GetFollowedThreads
7 | GET_THREAD=org.sab.thread.commands.GetThread
8 | MODERATOR_BANS_USER=org.sab.thread.commands.ModeratorBansUser
9 | UPDATE_THREAD=org.sab.thread.commands.UpdateThread
10 |
--------------------------------------------------------------------------------
/apps/search_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=search_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=search_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/search_app.jar"]
22 |
--------------------------------------------------------------------------------
/apps/thread_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=thread_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15
17 | ENV APP_NAME=thread_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/thread_app.jar"]
23 |
--------------------------------------------------------------------------------
/apps/user_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=user_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=user_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/user_app.jar"]
23 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/CouchbaseBuckets.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum CouchbaseBuckets {
4 | LISTINGS("Listings"),
5 | RECOMMENDED_SUB_THREADS("RecommendedSubThreads"),
6 | RECOMMENDED_THREADS("RecommendedThreads"),
7 | RECOMMENDED_USERS("RecommendedUsers"),
8 | COMMENTS("Comments");
9 |
10 |
11 | private final String bucketName;
12 |
13 | CouchbaseBuckets(String bucketName) {
14 | this.bucketName = bucketName;
15 | }
16 |
17 | public String get() {
18 | return this.bucketName;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/auth/Auth.java:
--------------------------------------------------------------------------------
1 | package org.sab.auth;
2 |
3 | import org.mindrot.jbcrypt.BCrypt;
4 |
5 | public class Auth {
6 | private Auth() {
7 | }
8 |
9 | public static String hash(String password) {
10 | return BCrypt.hashpw(password, BCrypt.gensalt(12));
11 | }
12 |
13 | public static boolean verifyHash(String password, String hash) {
14 | try {
15 | return BCrypt.checkpw(password, hash);
16 | } catch (IllegalArgumentException e) {
17 | return false;
18 | }
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/subthread_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=subthread_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15
17 | ENV APP_NAME=subthread_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/subthread_app.jar"]
23 |
--------------------------------------------------------------------------------
/notification-client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/libs/netty/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=netty
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=netty
18 |
19 | COPY --from=build /reddit/libs/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 | EXPOSE 8080
21 | EXPOSE 8443
22 |
23 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/netty.jar"]
24 |
--------------------------------------------------------------------------------
/libs/utilities/src/test/java/org/sab/functions/TriFunctionTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.functions;
2 | import org.junit.Test;
3 |
4 | import static org.junit.Assert.assertEquals;
5 |
6 | public class TriFunctionTest {
7 |
8 | @Test
9 | public void shouldAddTwoIntegers() {
10 | final TriFunction add = (x, y) -> x + y;
11 |
12 | final Integer x = 3;
13 | final Integer y = 7;
14 | final Integer expected = x + y;
15 | final Integer actual = add.apply(x, y);
16 |
17 | assertEquals(expected, actual);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=chat_storage
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=chat_storage
18 |
19 | COPY --from=build /reddit/apps/chat_app/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/chat_storage.jar"]
22 |
--------------------------------------------------------------------------------
/apps/recommendation_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=recommendation_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15
17 | ENV APP_NAME=recommendation_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/recommendation_app.jar"]
22 |
--------------------------------------------------------------------------------
/apps/notification_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=notification_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=notification_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/notification_app.jar"]
23 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/validation/Schema.java:
--------------------------------------------------------------------------------
1 | package org.sab.validation;
2 |
3 | import java.util.List;
4 |
5 | public class Schema {
6 | List attributeList;
7 |
8 | public Schema(List attributeList) {
9 | this.attributeList = attributeList;
10 | }
11 |
12 | public static Schema emptySchema() {
13 | return new Schema(List.of());
14 | }
15 |
16 | public List getAttributeList() {
17 | return attributeList;
18 | }
19 |
20 | public boolean isEmpty() {
21 | return attributeList.isEmpty();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=chat_server
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=chat_server
18 |
19 | COPY --from=build /reddit/apps/chat_app/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 | EXPOSE 5000
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/chat_server.jar"]
23 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/java/org/sab/chat/storage/exceptions/InvalidInputException.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage.exceptions;
2 |
3 | public class InvalidInputException extends Exception {
4 | static private final String DEFAULT_MESSAGE = "Inputs provided to are invalid.";
5 |
6 | public InvalidInputException() {
7 | super(DEFAULT_MESSAGE);
8 | }
9 |
10 | public InvalidInputException(Exception e) {
11 | super(DEFAULT_MESSAGE);
12 | e.printStackTrace();
13 | }
14 |
15 | public InvalidInputException(String message) {
16 | super(message);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/notification-client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | # Build stage
4 | FROM maven:3.8.1-openjdk-15-slim AS build
5 | ENV APP_NAME=user_to_user_actions_app
6 |
7 | COPY pom.xml /reddit/
8 | WORKDIR /reddit
9 |
10 | COPY libs libs
11 | COPY apps apps
12 |
13 | RUN mvn clean package -pl :${APP_NAME} -am -DskipTests
14 |
15 | # Package stage
16 | FROM openjdk:15-alpine
17 | ENV APP_NAME=user_to_user_actions_app
18 |
19 | COPY --from=build /reddit/apps/${APP_NAME}/target/${APP_NAME}-1.0-SNAPSHOT.jar /usr/local/lib/${APP_NAME}.jar
20 |
21 |
22 | ENTRYPOINT ["java", "-jar", "/usr/local/lib/user_to_user_actions_app.jar"]
23 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/functions/Utilities.java:
--------------------------------------------------------------------------------
1 | package org.sab.functions;
2 |
3 | public class Utilities {
4 | private Utilities() {
5 | }
6 |
7 | public static boolean isDevelopmentMode() {
8 | String mode = System.getenv("ENV_TYPE");
9 | return mode != null && mode.equals("Development");
10 | }
11 | public static boolean inContainerizationMode() {
12 | String mode = System.getenv("ENV_TYPE");
13 | return mode != null && mode.equals("Staging");
14 | }
15 |
16 | public static String formatUUID(String UUID) {
17 | return UUID.replaceAll("[-]", "");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/futures/FutureUtil.java:
--------------------------------------------------------------------------------
1 | package org.sab.futures;
2 |
3 | import java.util.concurrent.ExecutionException;
4 | import java.util.concurrent.Future;
5 | import java.util.function.Function;
6 |
7 | public class FutureUtil {
8 | private FutureUtil() {
9 | }
10 |
11 | public static T await(Future future, Function onFailure) {
12 | T result = null;
13 | try {
14 | result = future.get();
15 | } catch (InterruptedException | ExecutionException e) {
16 | return onFailure.apply(e);
17 | }
18 | return result;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/libs/couchbase/configure-server.sh:
--------------------------------------------------------------------------------
1 | set -m
2 |
3 | /entrypoint.sh couchbase-server &
4 |
5 |
6 | sleep 15
7 |
8 |
9 | # Setup initial cluster/ Initialize Node
10 | couchbase-cli cluster-init -c 127.0.0.1 --cluster-name $COUCHBASE_CLUSTER_NAME --cluster-username $COUCHBASE_ADMINISTRATOR_USERNAME \
11 | --cluster-password $COUCHBASE_ADMINISTRATOR_PASSWORD --services data --cluster-ramsize $COUCHBASE_RAM --index-storage-setting default \
12 |
13 | # Setup Administrator username and password
14 | curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=$COUCHBASE_ADMINISTRATOR_USERNAME -d password=$COUCHBASE_ADMINISTRATOR_PASSWORD
15 |
16 | fg 1
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/reflection/ReflectionUtils.java:
--------------------------------------------------------------------------------
1 | package org.sab.reflection;
2 |
3 | import java.lang.reflect.Method;
4 |
5 | public class ReflectionUtils {
6 | public static Method getMethod(Class clas, String methodName) {
7 | try {
8 | Method[] methods = clas.getMethods();
9 | for (Method method : methods) {
10 | if (method.getName().equalsIgnoreCase(methodName)) {
11 | return method;
12 | }
13 | }
14 | } catch (SecurityException e) {
15 | e.printStackTrace();
16 | }
17 | return null;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/handlers/ResponseHandler.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.handlers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import io.netty.channel.SimpleChannelInboundHandler;
5 | import org.json.simple.JSONObject;
6 | import org.sab.chat.server.ClientManager;
7 |
8 | public class ResponseHandler extends SimpleChannelInboundHandler {
9 |
10 | @Override
11 | protected void channelRead0(ChannelHandlerContext ctx, JSONObject queueResponse) {
12 | queueResponse.remove("statusCode");
13 | ClientManager.routeResponse((JSONObject) queueResponse.clone(), ctx);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/cli/readme.md:
--------------------------------------------------------------------------------
1 | # reddit-cli
2 |
3 | ## Install
4 | Change directory to `reddit/cli`, then run the following commands:
5 | ```bash
6 | $ npm i
7 | $ npm install --global .
8 | ```
9 |
10 | ## Using the CLI
11 |
12 | ```
13 | $ reddit-cli --help
14 |
15 | Usage
16 | $ reddit-cli
17 |
18 | Commands
19 | $ reddit-cli welcome
20 | $ reddit-cli chat
21 |
22 | Flags
23 | --user, -u specify the user id. Special user ids are joe, ouda, abu and ronic.
24 | --rainbow, -r Add rainbow welcome title
25 |
26 | Examples
27 | $ reddit-cli chat -u ee55dcf8-ee7b-429a-939e-12c2f7b7ddee
28 | $ reddit-cli chat -u abu
29 | $ reddit-cli welcome -r
30 | ```
31 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/Command.java:
--------------------------------------------------------------------------------
1 | package org.sab.service;
2 |
3 | import org.json.JSONObject;
4 |
5 | /**
6 | * Abstract class Command that is extended by all command classes.
7 | */
8 |
9 | public abstract class Command {
10 | private static double classVersion = 1.0;
11 |
12 | // The function that will be invoked by all classes implementing Command.
13 | public abstract String execute(JSONObject request);
14 |
15 | public static double getClassVersion() {
16 | return classVersion;
17 | }
18 |
19 | public static void setClassVersion(double classVersion) {
20 | Command.classVersion = classVersion;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/cli/flows/welcome-flow.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const React = require('react')
3 | const { Text, Static } = require('ink')
4 | const importJsx = require('import-jsx')
5 |
6 | const WelcomeHeader = importJsx('../components/welcome-header.js')
7 | const Counter = importJsx('../components/counter')
8 |
9 | const WelcomeFlow = ({ rainbow }) => (
10 |
11 |
12 | {(item) => (
13 |
14 | )}
15 |
16 |
17 | Hello, Stranger
18 |
19 |
20 |
21 | )
22 |
23 | module.exports = WelcomeFlow
24 |
--------------------------------------------------------------------------------
/apps/notification_app/src/main/java/org/sab/notification/NotificationApp.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification;
2 |
3 | import org.sab.service.Service;
4 |
5 | public class NotificationApp extends Service {
6 | public static final String TOKEN = "token";
7 | public static final String TOKENS_COLLECTION = "userTokens";
8 |
9 | public static void main(String[] args) {
10 | new NotificationApp().start();
11 | }
12 |
13 | public static String getNotificationsCollectionName(String user) {
14 | return "userNotifications/" + user + "/notifications";
15 | }
16 |
17 | @Override
18 | public String getAppUriName() {
19 | return "notification";
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | ADD_GROUP_MEMBER = org.sab.chat.commands.AddGroupMember
2 | CREATE_DIRECT_CHAT = org.sab.chat.commands.CreateDirectChat
3 | CREATE_DIRECT_MESSAGE = org.sab.chat.commands.CreateDirectMessage
4 | CREATE_GROUP_CHAT = org.sab.chat.commands.CreateGroupChat
5 | CREATE_GROUP_MESSAGE = org.sab.chat.commands.CreateGroupMessage
6 | GET_CHATS = org.sab.chat.commands.GetChats
7 | GET_DIRECT_MESSAGES = org.sab.chat.commands.GetDirectMessages
8 | GET_GROUP_MESSAGES = org.sab.chat.commands.GetGroupMessages
9 | LEAVE_GROUP = org.sab.chat.commands.LeaveGroup
10 | REMOVE_GROUP_MEMBER = org.sab.chat.commands.RemoveGroupMember
11 | INIT_CONNECTION = org.sab.chat.commands.GetChats
12 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - "15672:15672"
7 | - "5672:5672"
8 | arangodb:
9 | image: "arangodb"
10 | environment:
11 | - ARANGO_ROOT_PASSWORD=root
12 | ports:
13 | - "8529:8529"
14 | user_to_user_actions_app:
15 | build:
16 | context: "../../"
17 | dockerfile: "apps/user_to_user_actions_app/Dockerfile"
18 | restart: on-failure
19 | ports:
20 | - "4009:4009"
21 | env_file:
22 | - "../../.env"
23 |
24 | depends_on:
25 | - "rabbitmq"
26 | - "arangodb"
27 |
28 | networks:
29 | default:
30 | name: user_actions_nw
31 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/Service.java:
--------------------------------------------------------------------------------
1 | package org.sab.service;
2 |
3 | import org.sab.service.managers.ControlManager;
4 |
5 | /**
6 | * Abstract class service which will be extended by the main class of each mini-app.
7 | * Uses the Command Pattern.
8 | * Contains the threading and command invoking functionality.
9 | */
10 |
11 | public abstract class Service {
12 |
13 | private final ControlManager controlManager = new ControlManager(getAppUriName());
14 |
15 | public abstract String getAppUriName();
16 |
17 | public void start() {
18 | controlManager.start();
19 | }
20 |
21 | public ControlManager getControlManager() {
22 | return controlManager;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/example_app/src/test/java/org/sab/demo/CommandWithoutAuthTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo;
2 |
3 | import org.json.JSONObject;
4 | import org.junit.Test;
5 | import org.sab.demo.commands.CommandWithoutAuth;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | public class CommandWithoutAuthTest {
10 |
11 | @Test
12 | public void willSucceedWithoutAuth() {
13 | JSONObject request = new JSONObject().put("uriParams", new JSONObject()).put("methodType", "GET");
14 | JSONObject response = new JSONObject(new CommandWithoutAuth().execute(request));
15 | assertEquals(200, response.getInt("statusCode"));
16 | assertEquals("I don't need authentication!", response.getString("msg"));
17 | }
18 |
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/notification-client/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/CommandWithoutAuth.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import org.sab.service.Responder;
4 | import org.sab.service.validation.CommandWithVerification;
5 | import org.sab.service.validation.HTTPMethod;
6 | import org.sab.validation.Schema;
7 |
8 | public class CommandWithoutAuth extends CommandWithVerification {
9 |
10 |
11 | // isAuthNeeded is by default false
12 |
13 | @Override
14 | protected String execute() {
15 | return Responder.makeMsgResponse("I don't need authentication!");
16 | }
17 |
18 | @Override
19 | protected Schema getSchema() {
20 | return Schema.emptySchema();
21 | }
22 |
23 | @Override
24 | protected HTTPMethod getMethodType() {
25 | return HTTPMethod.GET;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/java/org/sab/chat/storage/config/KeyspaceInitializer.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage.config;
2 |
3 | import com.datastax.driver.core.Session;
4 |
5 | public class KeyspaceInitializer {
6 |
7 | public static void initializeKeyspace(Session session,
8 | String keyspaceName, String replicationStrategy, int replicationFactor) {
9 | String query = String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH " +
10 | "replication = {'class':'%s', 'replication_factor': %d};",
11 | keyspaceName, replicationStrategy, replicationFactor
12 | );
13 | session.execute(query);
14 |
15 | query = String.format("USE %s;", keyspaceName);
16 | session.execute(query);
17 |
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/libs/rabbitmq/src/main/java/org/sab/rabbitmq/RPCBase.java:
--------------------------------------------------------------------------------
1 | package org.sab.rabbitmq;
2 |
3 | import com.rabbitmq.client.Connection;
4 | import com.rabbitmq.client.ConnectionFactory;
5 |
6 | import java.io.IOException;
7 | import java.util.concurrent.TimeoutException;
8 |
9 |
10 | class RPCBase {
11 |
12 | private static final String LOCALHOST = "localhost";
13 |
14 | private RPCBase() {
15 | }
16 |
17 | public static Connection initConnection() throws TimeoutException, IOException {
18 | ConnectionFactory factory = new ConnectionFactory();
19 |
20 | String rabbitHost = System.getenv("RABBIT_HOST");
21 | if(rabbitHost == null)
22 | rabbitHost = LOCALHOST;
23 |
24 | factory.setHost(rabbitHost);
25 |
26 | return factory.newConnection();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/user_to_user_actions_app/src/main/java/org/sab/useractions/UserToUserActionsApp.java:
--------------------------------------------------------------------------------
1 | package org.sab.useractions;
2 |
3 | import org.sab.service.Service;
4 | import org.sab.arango.Arango;
5 |
6 | public class UserToUserActionsApp extends Service {
7 |
8 | public static void main(String[] args) {
9 | try {
10 | new UserToUserActionsApp().start();
11 | dbInit();
12 | } catch (Exception e) {
13 | e.printStackTrace();
14 | System.exit(-1);
15 | }
16 | }
17 |
18 | public static void dbInit() {
19 | Arango arango = Arango.getInstance();
20 | arango.createDatabaseIfNotExists(System.getenv("ARANGO_DB"));
21 | }
22 |
23 | @Override
24 | public String getAppUriName() {
25 | return "useraction";
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/libs/utilities/src/test/java/org/sab/environmentvariables/EnvVariablesUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.environmentvariables;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 | import static org.junit.Assert.fail;
7 |
8 | public class EnvVariablesUtilsTest {
9 |
10 | @Test
11 | public void getEnvOrThrowThrowsNullPointerIfEnvVariableDoesNotExist(){
12 | String dummyEnvVariable = "ThisEnvVariableDoesNotExist";
13 | try {
14 | EnvVariablesUtils.getEnvOrThrow(dummyEnvVariable);
15 | fail("This test should have thrown an exception, but it didn't !");
16 | }
17 | catch (NullPointerException e){
18 | assertEquals(String.format("The Environment Variable \"%s\" cannot be null", dummyEnvVariable),e.getMessage());
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/minio/FileSimulation.java:
--------------------------------------------------------------------------------
1 | package org.sab.minio;
2 |
3 | import org.json.JSONObject;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 |
8 | public class FileSimulation {
9 | private static final String SAMPLE_IMAGE_NAME = "SampleImage.png";
10 | private static final String SAMPLE_IMAGE_TYPE = "image/png";
11 |
12 | public static JSONObject generateImageJson() throws IOException {
13 | InputStream is = FileSimulation.class.getClassLoader().getResourceAsStream(SAMPLE_IMAGE_NAME);
14 | if (is != null) {
15 | String data = org.apache.commons.codec.binary.Base64.encodeBase64String(is.readAllBytes());
16 | return new JSONObject().put("data", data).put("type", SAMPLE_IMAGE_TYPE);
17 | } else
18 | return null;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/CommandNeedingAuth.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import org.sab.service.Responder;
4 | import org.sab.service.validation.CommandWithVerification;
5 | import org.sab.service.validation.HTTPMethod;
6 | import org.sab.validation.Schema;
7 |
8 | public class CommandNeedingAuth extends CommandWithVerification {
9 |
10 | @Override
11 | protected boolean isAuthNeeded() {
12 | return true;
13 | }
14 |
15 | @Override
16 | protected String execute() {
17 | return Responder.makeMsgResponse("Authentication successful!");
18 | }
19 |
20 | @Override
21 | protected Schema getSchema() {
22 | return Schema.emptySchema();
23 | }
24 |
25 | @Override
26 | protected HTTPMethod getMethodType() {
27 | return HTTPMethod.GET;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/apps/recommendation_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | GET_POPULAR_SUBTHREADS=org.sab.recommendation.commands.GetPopularSubThreads
2 | GET_POPULAR_THREADS=org.sab.recommendation.commands.GetPopularThreads
3 | GET_RECOMMENDED_SUBTHREADS=org.sab.recommendation.commands.GetRecommendedSubThreads
4 | GET_RECOMMENDED_THREADS=org.sab.recommendation.commands.GetRecommendedThreads
5 | GET_RECOMMENDED_USERS=org.sab.recommendation.commands.GetRecommendedUsers
6 | UPDATE_POPULAR_SUBTHREADS=org.sab.recommendation.commands.UpdatePopularSubThreads
7 | UPDATE_POPULAR_THREADS=org.sab.recommendation.commands.UpdatePopularThreads
8 | UPDATE_RECOMMENDED_SUBTHREADS=org.sab.recommendation.commands.UpdateRecommendedSubThreads
9 | UPDATE_RECOMMENDED_THREADS=org.sab.recommendation.commands.UpdateRecommendedThreads
10 | UPDATE_RECOMMENDED_USERS=org.sab.recommendation.commands.UpdateRecommendedUsers
11 |
--------------------------------------------------------------------------------
/apps/notification_app/src/main/java/org/sab/notification/FirebaseInitializer.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification;
2 |
3 | import com.google.auth.oauth2.GoogleCredentials;
4 | import com.google.firebase.FirebaseApp;
5 | import com.google.firebase.FirebaseOptions;
6 |
7 | public class FirebaseInitializer {
8 | private static boolean initialized;
9 |
10 | public static boolean initialize() {
11 | try {
12 | if (!initialized) {
13 | FirebaseOptions options = FirebaseOptions.builder()
14 | .setCredentials(GoogleCredentials.getApplicationDefault()).build();
15 | FirebaseApp.initializeApp(options);
16 | initialized = true;
17 | }
18 | } catch (Exception e) {
19 | e.printStackTrace();
20 | return false;
21 | }
22 |
23 | return true;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/libs/rabbitmq/src/main/java/org/sab/rabbitmq/SingleBasicChannel.java:
--------------------------------------------------------------------------------
1 | package org.sab.rabbitmq;
2 |
3 | import com.rabbitmq.client.Channel;
4 |
5 | import java.io.IOException;
6 | import java.util.concurrent.TimeoutException;
7 |
8 | abstract class SingleBasicChannel implements AutoCloseable {
9 | protected final Channel channel;
10 |
11 | public SingleBasicChannel(Channel channel) {
12 | this.channel = channel;
13 | }
14 |
15 | protected String declareSpecificQueue(String queue) throws IOException {
16 | final boolean durable = false;
17 | final boolean exclusive = false;
18 | final boolean autoDelete = false;
19 | return channel.queueDeclare(queue, durable, exclusive, autoDelete, null).getQueue();
20 | }
21 |
22 |
23 | @Override
24 | public void close() throws IOException, TimeoutException {
25 | channel.close();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/libs/postgres/src/main/resources/sql/DropUserProcedures.sql:
--------------------------------------------------------------------------------
1 | DROP FUNCTION IF EXISTS create_user (user_id VARCHAR,
2 | username VARCHAR,
3 | email VARCHAR,
4 | password VARCHAR,
5 | birthdate date,
6 | photo_url VARCHAR);
7 |
8 |
9 | DROP FUNCTION IF EXISTS delete_user(in_username VARCHAR);
10 |
11 |
12 | DROP FUNCTION IF EXISTS update_user_password(in_username VARCHAR, new_password VARCHAR);
13 |
14 | DROP FUNCTION IF EXISTS get_user(in_username VARCHAR);
15 |
16 |
17 | DROP FUNCTION IF EXISTS update_profile_picture(in_username VARCHAR, new_photo_url text);
18 |
19 |
20 | DROP FUNCTION IF EXISTS delete_profile_picture(in_username VARCHAR);
21 |
22 | DROP FUNCTION IF EXISTS is_username_deleted(VARCHAR);
23 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/environmentvariables/EnvVariablesUtils.java:
--------------------------------------------------------------------------------
1 | package org.sab.environmentvariables;
2 |
3 | import java.util.Objects;
4 |
5 | public class EnvVariablesUtils {
6 |
7 | public static String getOrDefaultEnvVariable(String envVariable, String defaultValue) {
8 | String value = System.getenv(envVariable);
9 | return value == null ? defaultValue : value;
10 | }
11 |
12 | /**
13 | * gets an environment variable if exists and throws a null pointer exception if it's not found
14 | *
15 | * @return System.getenv {@code envVariable}
16 | * @throws NullPointerException if {@code envVariable} is not found
17 | */
18 | public static String getEnvOrThrow(String envVariable) {
19 | return Objects.requireNonNull(System.getenv(envVariable), String.format("The Environment Variable \"%s\" cannot be null", envVariable));
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/report/SubThreadReportAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models.report;
2 |
3 | public enum SubThreadReportAttributes {
4 |
5 | // variables in a Report object
6 | Report_Id("id", "Id"),
7 |
8 |
9 | SUBTHREAD_Id("subthreadId", "SubthreadId"),
10 | DATE_CREATED("dateCreated", "DateCreated"),
11 |
12 | REPORTER_ID("userId", "UserId"),
13 | TYPE_OF_REPORT ("typeOfReport", "TypeOfReport"),
14 |
15 |
16 | PARENT_THREAD_ID("threadId", "ThreadId"),
17 | REPORT_MSG("reportMsg", "ReportMessage");
18 |
19 |
20 | private final String http;
21 | private final String db;
22 |
23 | SubThreadReportAttributes(String http, String db) {
24 | this.http = http;
25 | this.db = db;
26 | }
27 |
28 | public String getHTTP() {
29 | return http;
30 | }
31 |
32 | public String getDb() {
33 | return db;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/CommandNeedingClaims.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import org.sab.service.Responder;
4 | import org.sab.service.validation.CommandWithVerification;
5 | import org.sab.service.validation.HTTPMethod;
6 | import org.sab.validation.Schema;
7 |
8 | public class CommandNeedingClaims extends CommandWithVerification {
9 |
10 | @Override
11 | protected String execute() {
12 | String username = authenticationParams.getString("username");
13 | return Responder.makeMsgResponse(String.format("Hello %s!", username));
14 | }
15 |
16 | @Override
17 | protected Schema getSchema() {
18 | return Schema.emptySchema();
19 | }
20 |
21 | @Override
22 | protected HTTPMethod getMethodType() {
23 | return HTTPMethod.GET;
24 | }
25 |
26 | @Override
27 | protected boolean isAuthNeeded(){
28 | return true;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/cli/components/message.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text, Box, Spacer } = require('ink')
3 | const { useContext } = require('react')
4 | const AppContext = require('../contexts/app-context')
5 |
6 | const moment = require('moment')
7 | const { mapIdToSpecialId } = require('../utils/id-mapper')
8 |
9 | const Message = ({ content, senderId, timestamp }) => {
10 | const { userId } = useContext(AppContext)
11 | const isYourMessage = senderId === userId
12 | return (
13 |
14 |
15 |
16 | {(isYourMessage ? 'You' : mapIdToSpecialId(senderId)) + ': '}
17 |
18 |
19 |
20 | {content}
21 |
22 |
23 |
24 | {` (${moment(timestamp).calendar()})`}
25 |
26 |
27 | )
28 | }
29 |
30 | module.exports = Message
31 |
--------------------------------------------------------------------------------
/apps/search_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - 15672:15672
7 | arangodb:
8 | image: "arangodb"
9 | ports:
10 | - 8529:8529
11 | volumes:
12 | - arangodb_data:/var/lib/arangodb3
13 | - arangodb_apps_data:/var/lib/arangodb3-apps
14 | environment:
15 | - ARANGO_ROOT_PASSWORD=root
16 | search_app:
17 | build:
18 | context: "../../"
19 | dockerfile: "apps/search_app/Dockerfile"
20 | restart: on-failure
21 | ports:
22 | - "4005:4005"
23 | environment:
24 | - RABBIT_HOST=rabbitmq
25 | - ARANGO_DB=Reddit
26 | - ARANGO_USER=root
27 | - ARANGO_PASSWORD=root
28 | - ARANGO_HOST=arangodb
29 | depends_on:
30 | - "arangodb"
31 | - "rabbitmq"
32 |
33 | networks:
34 | default:
35 | name: search_nw
36 |
37 | volumes:
38 | arangodb_data:
39 | arangodb_apps_data:
40 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/validation/Attribute.java:
--------------------------------------------------------------------------------
1 | package org.sab.validation;
2 |
3 | public class Attribute {
4 |
5 | String attributeName;
6 | DataType dataType;
7 | boolean isRequired = false;
8 |
9 | public String getAttributeName() {
10 | return attributeName;
11 | }
12 |
13 | public DataType getDataType() {
14 | return dataType;
15 | }
16 |
17 | public boolean isRequired() {
18 | return isRequired;
19 | }
20 |
21 | public Attribute(String attributeName, DataType dataType) {
22 | this(attributeName, dataType, false);
23 | }
24 |
25 | public Attribute(String attributeName, DataType dataType, boolean isRequired) {
26 | this.attributeName = attributeName;
27 | this.dataType = dataType;
28 | this.isRequired = isRequired;
29 | }
30 |
31 | public boolean isValidlyTyped(Object object) {
32 | return dataType.isOfValidType(object);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/ThreadAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum ThreadAttributes {
4 | // variables in a thread object
5 | THREAD_NAME("name", "Name"),
6 | DESCRIPTION("description", "Description"),
7 | CREATOR_ID("creatorId", "CreatorId"),
8 | NUM_OF_FOLLOWERS("numOfFollowers", "NumOfFollowers"),
9 | DATE_CREATED("dateCreated", "DateCreated"),
10 |
11 | // additional request parameters
12 | ASSIGNER_ID("assignerId", null),
13 | MODERATOR_ID("moderatorId", null),
14 | ACTION_MAKER_ID("userId", "UserId"),
15 | BANNED_USER_ID("bannedUserId", "BannedUserId");
16 |
17 |
18 | private final String http;
19 | private final String db;
20 |
21 | ThreadAttributes(String http, String db) {
22 | this.http = http;
23 | this.db = db;
24 | }
25 |
26 | public String getHTTP() {
27 | return http;
28 | }
29 |
30 | public String getDb() {
31 | return db;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/SubThreadAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum SubThreadAttributes {
4 |
5 | // variables in a subthread object
6 | SUBTHREAD_ID("id", "Id"),
7 | PARENT_THREAD_ID("parentThreadId", "ParentThreadId"),
8 | CREATOR_ID("creatorId", "CreatorId"),
9 |
10 | TITLE("title", "Title"),
11 | CONTENT("content", "Content"),
12 |
13 | LIKES("likes", "Likes"),
14 | DISLIKES("dislikes", "Dislikes"),
15 |
16 | HAS_IMAGE("hasImage", "HasImage"),
17 |
18 | DATE_CREATED("dateCreated", "DateCreated"),
19 |
20 | // additional attributes
21 | ACTION_MAKER_ID("userId", null);
22 |
23 | private final String http;
24 | private final String db;
25 |
26 | SubThreadAttributes(String http, String db) {
27 | this.http = http;
28 | this.db = db;
29 | }
30 |
31 | public String getHTTP() {
32 | return http;
33 | }
34 |
35 | public String getDb() {
36 | return db;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/io/IoUtils.java:
--------------------------------------------------------------------------------
1 | package org.sab.io;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.handler.codec.base64.Base64;
5 |
6 | import java.io.ByteArrayInputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.nio.charset.StandardCharsets;
10 |
11 | import static io.netty.buffer.Unpooled.copiedBuffer;
12 |
13 | public class IoUtils {
14 |
15 | public static String encodeFile(ByteBuf byteBuf) {
16 | return Base64.encode(byteBuf).toString(StandardCharsets.UTF_8);
17 | }
18 |
19 | public static String encodeFile(InputStream inputStream) throws IOException {
20 | byte[] bytes = inputStream.readAllBytes();
21 | return encodeFile(copiedBuffer(bytes));
22 | }
23 |
24 | public static InputStream decodeFile(String encodedFile) {
25 | byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(encodedFile);
26 | InputStream stream = new ByteArrayInputStream(bytes);
27 | return stream;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libs/rabbitmq/src/main/java/org/sab/rabbitmq/RPCClient.java:
--------------------------------------------------------------------------------
1 | package org.sab.rabbitmq;
2 |
3 | import java.io.IOException;
4 | import java.util.concurrent.TimeoutException;
5 |
6 | import com.rabbitmq.client.Connection;
7 |
8 | public class RPCClient implements AutoCloseable {
9 |
10 | private static RPCClient instance = null;
11 | private final Connection connection;
12 |
13 | // creating a connection with RabbitMQ
14 | private RPCClient() throws IOException, TimeoutException {
15 | super();
16 | connection = RPCBase.initConnection();
17 | }
18 |
19 | public static SingleClientChannel getSingleChannelExecutor() throws IOException, TimeoutException {
20 | if (instance == null) {
21 | instance = new RPCClient();
22 | }
23 |
24 | return new SingleClientChannel(instance.connection.createChannel());
25 | }
26 |
27 | // Close the |channel| as to not waste resources.
28 | public void close() throws IOException {
29 | connection.close();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/CommentAttributes.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum CommentAttributes {
4 |
5 | // variables in a Comment object
6 | COMMENT_ID("commentId", "CommentId"),
7 | CREATOR_ID("creatorId", "CreatorId"),
8 |
9 | // TODO parent could be a comment...
10 | PARENT_SUBTHREAD_ID("parentSubthreadId", "ParentSubthreadId"),
11 | PARENT_CONTENT_TYPE("parentContentType", "ParentContentType"),
12 |
13 | CONTENT("content", "Content"),
14 | DATE_CREATED("dateCreated", "DateCreated"),
15 |
16 | LIKES("likes", "Likes"),
17 | DISLIKES("dislikes", "Dislikes"),
18 |
19 | // additional request parameters
20 | ACTION_MAKER_ID("userId", null);
21 |
22 | private final String http;
23 | private final String db;
24 |
25 | CommentAttributes(String http, String db) {
26 | this.http = http;
27 | this.db = db;
28 | }
29 |
30 | public String getHTTP() {
31 | return http;
32 | }
33 |
34 | public String getDb() {
35 | return db;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/ServiceConstants.java:
--------------------------------------------------------------------------------
1 | package org.sab.service;
2 |
3 | import java.util.ArrayList;
4 |
5 | public class ServiceConstants {
6 |
7 | private ServiceConstants () {
8 | }
9 |
10 | public static final String COMMAND_MAP_FILENAME = "commandMap.properties".toLowerCase();
11 |
12 | public static final String REQUEST_QUEUE_NAME_SUFFIX = "_REQ";
13 | public static final int MAX_THREAD_TIMEOUT = 4;
14 |
15 | public static final String THREADS_COUNT_PROPERTY_NAME = "threadsCount";
16 | public static final int DEFAULT_THREADS_COUNT = 10;
17 |
18 | public static final String REQUIRED_DATABASES_PROPERTY_NAME = "requiredDatabases";
19 | public static final String REQUIRED_DATABASES_ARRAY_DELIMITER = ",";
20 | public static final String REQUIRED_DATABASES_PAIR_DELIMITER = "-";
21 | public static final ArrayList DEFAULT_REQUIRED_DATABASES = new ArrayList<>();
22 |
23 | public static final int DEFAULT_CONNECTION_COUNT = 10;
24 |
25 | public static final String GET_DB_CLIENT_METHOD_NAME = "getInstance";
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/apps/chat_app/README.md:
--------------------------------------------------------------------------------
1 | # Chat App
2 |
3 | The chatting application of Reddit.
4 |
5 | ## Installing and Running Dependencies
6 |
7 | You must have docker installed for running the next commands.
8 |
9 | ### Install and Run Cassandra
10 |
11 | ```bash
12 | docker run -d \ -p 9042:9042 \ --name cassandra \ cassandra:latest
13 | ```
14 |
15 | #### Install and Run Cassandra Web (optional)
16 |
17 | If you want to view the chat database through online UI
18 | ```bash
19 | docker run -d \
20 | -e CASSANDRA_HOST_IP=$(docker inspect --format '{{.NetworkSettings.IPAddress}}' cassandra) \
21 | -e CASSANDRA_PORT=9042 \
22 | -p 3000:3000 \
23 | --name cassandra-web \
24 | delermando/docker-cassandra-web:v0.4.0
25 | ```
26 | You can view the UI on `http://localhost:3000/`.
27 |
28 | ### Install and Run RabbitMQ
29 |
30 | ```bash
31 | docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:management
32 | ```
33 |
34 | ## Running Chat App
35 |
36 | Now all you have to do is run the `ChatServer` class in `chat_server` module and `ChatStorageApp` class in `chat_storage` module, then enjoy using the chatting app.
37 |
38 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/RouterBuilder.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | public class RouterBuilder {
4 |
5 | public static Router createRouter(String routerType) {
6 | return switch (routerType) {
7 | case "INIT_CONNECTION" -> new InitConnectionRouter();
8 | case "ADD_GROUP_MEMBER" -> new AddMemberRouter();
9 | case "REMOVE_GROUP_MEMBER" -> new RemoveMemberRouter();
10 | case "LEAVE_GROUP" -> new LeaveGroupRouter();
11 | case "GET_CHATS" -> new GetChatsRouter();
12 | case "GET_DIRECT_MESSAGES" -> new GetDirectMessagesRouter();
13 | case "GET_GROUP_MESSAGES" -> new GetGroupMessagesRouter();
14 | case "CREATE_GROUP_MESSAGE" -> new CreateGroupMessageRouter();
15 | case "CREATE_GROUP_CHAT" -> new CreateGroupChatRouter();
16 | case "CREATE_DIRECT_CHAT" -> new CreateDirectChatRouter();
17 | case "CREATE_DIRECT_MESSAGE" -> new CreateDirectMessageRouter();
18 | default -> null;
19 | };
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/main/java/org/sab/chat/storage/tables/TableUtils.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage.tables;
2 |
3 | import com.datastax.driver.core.Row;
4 |
5 | import java.time.Instant;
6 | import java.util.UUID;
7 | import java.util.List;
8 |
9 | public class TableUtils {
10 | public static boolean isEmpty(List rows) {
11 | return rows == null || rows.size() == 0;
12 | }
13 |
14 | private static final long NUM_HUNDRED_NANOS_IN_A_SECOND = 10_000_000L;
15 |
16 | private static final long NUM_HUNDRED_NANOS_FROM_UUID_EPOCH_TO_UNIX_EPOCH = 122_192_928_000_000_000L;
17 |
18 | public static Instant getInstantFromUUID(final UUID uuid) {
19 | final long hundredNanosSinceUnixEpoch = uuid.timestamp() - NUM_HUNDRED_NANOS_FROM_UUID_EPOCH_TO_UNIX_EPOCH;
20 | final long secondsSinceUnixEpoch = hundredNanosSinceUnixEpoch / NUM_HUNDRED_NANOS_IN_A_SECOND;
21 | final long nanoAdjustment = ((hundredNanosSinceUnixEpoch % NUM_HUNDRED_NANOS_IN_A_SECOND) * 100);
22 | return Instant.ofEpochSecond(secondsSinceUnixEpoch, nanoAdjustment);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/cli/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 | process.env.NODE_ENV = 'production';
4 |
5 | const React = require('react')
6 | const importJsx = require('import-jsx')
7 | const { render } = require('ink')
8 | const meow = require('meow')
9 |
10 | const App = importJsx('./app')
11 |
12 |
13 | const cli = meow(
14 | `
15 | Usage
16 | $ reddit-cli
17 |
18 | Commands
19 | $ reddit-cli welcome
20 | $ reddit-cli chat
21 |
22 | Flags
23 | --user, -u specify the user id. Special user ids are joe, ouda, abu and ronic.
24 | --rainbow, -r Add rainbow welcome title
25 |
26 | Examples
27 | $ reddit-cli chat -u ee55dcf8-ee7b-429a-939e-12c2f7b7ddee
28 | $ reddit-cli chat -u abu
29 | $ reddit-cli welcome -r
30 | `,
31 | {
32 | flags: {
33 | user: {
34 | type: 'string',
35 | alias: 'u'
36 | },
37 | rainbow: {
38 | type: 'boolean',
39 | alias: 'r'
40 | }
41 | }
42 | }
43 | )
44 |
45 | render(
46 | React.createElement(App, {
47 | command: cli.input[0],
48 | flags: {
49 | user: cli.flags.user,
50 | rainbow: cli.flags.rainbow
51 | }
52 | })
53 | )
54 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/databases/PooledDatabaseClient.java:
--------------------------------------------------------------------------------
1 | package org.sab.databases;
2 |
3 | /**
4 | * This class assumes that the DB client is following a singleton pattern
5 | * However there is no clean way to force the inheritance of singletons
6 | *
7 | * Thus, we assume that the concept of "consenting adults" applies and
8 | * that the concrete classes will follow without an explicit contract
9 | *
10 | * Check commented compulsory method below
11 | * */
12 | public interface PooledDatabaseClient {
13 |
14 | // Note: a decorator pattern would be a better design strategy
15 | void createPool(int maxConnections);
16 | void destroyPool() throws PoolDoesNotExistException;
17 | void setMaxConnections(int maxConnections) throws PoolDoesNotExistException;
18 | String getName();
19 |
20 | /**
21 | * Compulsory method!
22 | * Every concrete class that implements this interface needs to implement this method
23 | *
24 | * ConcreteClass should be replaced by the DB client type Ex. Arango
25 | * */
26 | // public static ConcreteClass getInstance()
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/strings/StringManipulation.java:
--------------------------------------------------------------------------------
1 | package org.sab.strings;
2 |
3 | public class StringManipulation {
4 | private StringManipulation() {
5 | }
6 |
7 | public static String camelToPascalCase(String camelCase) {
8 | char[] copy = camelCase.toCharArray();
9 | copy[0] = (copy[0] + "").toUpperCase().charAt(0);
10 | return new String(copy);
11 | }
12 |
13 | public static String camelToSnakeCase(String camelcase) {
14 | StringBuilder snakeCase = new StringBuilder();
15 | for (char c : camelcase.toCharArray())
16 | if (isCapital(c))
17 | snakeCase.append("_").append((c + "").toLowerCase());
18 | else
19 | snakeCase.append(c);
20 | return snakeCase.toString();
21 |
22 | }
23 |
24 | public static String pascalToScreamingCase(String pascalCase) {
25 | return camelToSnakeCase((pascalCase.charAt(0) + "").toLowerCase() + pascalCase.substring(1));
26 | }
27 |
28 | public static boolean isCapital(char c) {
29 | return c >= 'A' && c <= 'Z';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/Responder.java:
--------------------------------------------------------------------------------
1 | package org.sab.service;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | public class Responder {
7 | private Responder() {
8 | }
9 |
10 | public static String makeErrorResponse(String msg, int statusCode) {
11 | JSONObject error = new JSONObject().put("msg", msg).put("statusCode", statusCode);
12 | return error.toString();
13 | }
14 |
15 | public static String makeDataResponse(JSONObject data) {
16 | JSONObject response = new JSONObject().put("data", data);
17 | response.put("statusCode", 200);
18 | return response.toString();
19 | }
20 |
21 | public static String makeMsgResponse(String msg) {
22 | JSONObject response = new JSONObject().put("msg", msg);
23 | response.put("statusCode", 200);
24 | return response.toString();
25 | }
26 |
27 | public static JSONObject makeDataResponse(JSONArray data) {
28 | JSONObject response = new JSONObject().put("data", data);
29 | response.put("statusCode", 200);
30 | return response;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/GetGroupMessagesRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | public class GetGroupMessagesRouter extends Router {
8 |
9 | @Override
10 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
11 | boolean isAuthenticated = authenticate(request, "userId");
12 | if (!isAuthenticated) {
13 | rejectUnAuthenticatedRequest(ctx);
14 | return;
15 | }
16 | String[] attributes = {"chatId", "userId"};
17 | packAndForwardRequest(ctx, request, attributes);
18 | }
19 |
20 | @Override
21 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
22 | JSONObject data = (JSONObject) response.get("data");
23 | if(data == null) {
24 | handleError(ctx, response);
25 | return;
26 | }
27 |
28 | ClientManager.sendResponseToChannel(ctx.channel(), response);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/managers/ThreadPoolManager.java:
--------------------------------------------------------------------------------
1 | package org.sab.service.managers;
2 |
3 | import org.sab.service.ServiceConstants;
4 |
5 | import java.util.concurrent.*;
6 |
7 | public class ThreadPoolManager {
8 | private ExecutorService threadPool = null;
9 |
10 | public void initThreadPool(int threadCount) {
11 | threadPool = Executors.newFixedThreadPool(threadCount);
12 | }
13 |
14 | public void releaseThreadPool() {
15 | if (threadPool == null) {
16 | return;
17 | }
18 |
19 | try {
20 | threadPool.shutdown();
21 | if (!threadPool.awaitTermination(ServiceConstants.MAX_THREAD_TIMEOUT, TimeUnit.MINUTES)) {
22 | threadPool.shutdownNow();
23 | }
24 | } catch (InterruptedException e) {
25 | threadPool.shutdownNow();
26 | }
27 | }
28 |
29 | public void dispose() {
30 | releaseThreadPool();
31 | threadPool = null;
32 | }
33 |
34 | public Future submit(Callable callable) {
35 | return threadPool.submit(callable);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/notification-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^11.2.7",
9 | "@testing-library/user-event": "^12.8.3",
10 | "axios": "^0.21.1",
11 | "bootstrap": "^5.0.2",
12 | "firebase": "^8.6.8",
13 | "react": "^17.0.2",
14 | "react-bootstrap": "^1.6.1",
15 | "react-dom": "^17.0.2",
16 | "react-scripts": "4.0.3",
17 | "web-vitals": "^1.1.2"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/GetChatsRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class GetChatsRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "userId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"userId"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 |
30 | ClientManager.sendResponseToChannel(ctx.channel(), response);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/GetDirectMessagesRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class GetDirectMessagesRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "userId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "userId"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 |
30 | ClientManager.sendResponseToChannel(ctx.channel(), response);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/tests/TestsUtils.java:
--------------------------------------------------------------------------------
1 | package org.sab.tests;
2 |
3 | import org.json.JSONObject;
4 | import org.sab.auth.AuthParamsHandler;
5 |
6 | import java.util.Map;
7 |
8 | public class TestsUtils {
9 | public static JSONObject makeRequest(JSONObject body, String methodType, JSONObject uriParams) {
10 | JSONObject request = new JSONObject();
11 | request.put("body", body);
12 | request.put("methodType", methodType);
13 | request.put("uriParams", uriParams);
14 | return request;
15 | }
16 |
17 | public static JSONObject makeAuthorizedRequest(JSONObject body, String methodType, JSONObject uriParams) {
18 | JSONObject unAuthorizedRequest = makeRequest(body, methodType, uriParams);
19 | return AuthParamsHandler.putAuthorizedParams(unAuthorizedRequest);
20 | }
21 |
22 | public static JSONObject makeAuthorizedRequest(JSONObject body, String methodType, JSONObject uriParams, String username) {
23 | JSONObject unAuthorizedRequest = makeRequest(body, methodType, uriParams);
24 | return AuthParamsHandler.putAuthorizedParams(unAuthorizedRequest, Map.of("username", username));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/thread_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - "15672:15672"
7 | - "5672:5672"
8 | arangodb:
9 | image: "arangodb"
10 | env_file:
11 | - "../../.env"
12 | ports:
13 | - "8529:8529"
14 | couchbase:
15 | build:
16 | context: "../../"
17 | dockerfile: "libs/couchbase/Dockerfile"
18 | environment:
19 | - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator
20 | - COUCHBASE_ADMINISTRATOR_PASSWORD=password
21 | - COUCHBASE_CLUSTER_NAME=Reddit
22 | - COUCHBASE_RAM=1024
23 | ports:
24 | - 8091-8094:8091-8094
25 | - 11210:11210
26 | thread_app:
27 | build:
28 | context: "../../"
29 | dockerfile: "apps/thread_app/Dockerfile"
30 | restart: on-failure
31 | ports:
32 | - "4007:4007"
33 | environment:
34 | - ARANGO_HOST=arangodb
35 | - COUCHBASE_HOST=couchbase
36 | - RABBIT_HOST=rabbitmq
37 | env_file:
38 | - "../../.env"
39 |
40 | depends_on:
41 | - "rabbitmq"
42 | - "arangodb"
43 | - "couchbase"
44 |
45 | networks:
46 | default:
47 | name: thread_nw
48 |
--------------------------------------------------------------------------------
/apps/subthread_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - "15672:15672"
7 | - "5672:5672"
8 | arangodb:
9 | image: "arangodb"
10 | env_file:
11 | - "../../.env"
12 | ports:
13 | - "8529:8529"
14 | couchbase:
15 | build:
16 | context: "../../"
17 | dockerfile: "libs/couchbase/Dockerfile"
18 | environment:
19 | - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator
20 | - COUCHBASE_ADMINISTRATOR_PASSWORD=password
21 | - COUCHBASE_CLUSTER_NAME=Reddit
22 | - COUCHBASE_RAM=1024
23 | ports:
24 | - 8091-8094:8091-8094
25 | - 11210:11210
26 | subthread_app:
27 | build:
28 | context: "../../"
29 | dockerfile: "apps/subthread_app/Dockerfile"
30 | restart: on-failure
31 | ports:
32 | - "4006:4006"
33 | environment:
34 | - ARANGO_HOST=arangodb
35 | - COUCHBASE_HOST=couchbase
36 | - RABBIT_HOST=rabbitmq
37 | env_file:
38 | - "../../.env"
39 |
40 | depends_on:
41 | - "rabbitmq"
42 | - "arangodb"
43 | - "couchbase"
44 |
45 | networks:
46 | default:
47 | name: subthread_nw
48 |
--------------------------------------------------------------------------------
/apps/chat_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - "15672:15672"
7 | cassandra:
8 | image: "cassandra"
9 | chat_server:
10 | build:
11 | context: "../../"
12 | dockerfile: "apps/chat_app/chat_server/Dockerfile"
13 | env_file:
14 | - .env
15 | environment:
16 | - RABBIT_HOST=rabbitmq
17 | ports:
18 | - "5000:5000"
19 | depends_on:
20 | - "rabbitmq"
21 | chat_storage:
22 | build:
23 | context: "../../"
24 | dockerfile: "apps/chat_app/chat_storage/Dockerfile"
25 | restart: on-failure
26 | ports:
27 | - "4001:4001"
28 | environment:
29 | - RABBIT_HOST=rabbitmq
30 | - CASSANDRA_NODE=cassandra
31 | - CASSANDRA_PORT=9042
32 | - KEYSPACE_NAME=chat_app
33 | - REPLICATION_STRATEGY=SimpleStrategy
34 | - REPLICATION_FACTOR=2
35 | depends_on:
36 | - "cassandra"
37 | - "rabbitmq"
38 |
39 | networks:
40 | default:
41 | name: chat_nw
42 |
--------------------------------------------------------------------------------
/libs/service/src/main/java/org/sab/service/databases/DBConfig.java:
--------------------------------------------------------------------------------
1 | package org.sab.service.databases;
2 |
3 | import org.sab.databases.PooledDatabaseClient;
4 |
5 | public class DBConfig {
6 | private int connectionCount;
7 | private PooledDatabaseClient client;
8 |
9 | public DBConfig(int connectionCount) {
10 | this.connectionCount = connectionCount;
11 | }
12 |
13 | public String getClientName() {
14 | return client == null ? "No client initialized yet" : client.getName();
15 | }
16 |
17 | public int getConnectionCount() {
18 | return connectionCount;
19 | }
20 |
21 | public void setConnectionCount(int connectionCount) {
22 | this.connectionCount = connectionCount;
23 | }
24 |
25 | public PooledDatabaseClient getClient() {
26 | return client;
27 | }
28 |
29 | public void setClient(PooledDatabaseClient client) {
30 | this.client = client;
31 | }
32 |
33 | @Override
34 | public String toString() {
35 | return "DBConfig{" +
36 | "name='" + getClientName() + '\'' +
37 | ", connectionCount=" + connectionCount +
38 | ", dbClient=" + client +
39 | '}';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/libs/rabbitmq/src/main/java/org/sab/rabbitmq/RPCServer.java:
--------------------------------------------------------------------------------
1 | package org.sab.rabbitmq;
2 |
3 | import com.rabbitmq.client.Connection;
4 | import org.json.JSONObject;
5 | import org.sab.functions.TriFunction;
6 |
7 | import java.io.IOException;
8 | import java.util.concurrent.TimeoutException;
9 |
10 | public class RPCServer {
11 | private static RPCServer instance = null;
12 | private final Connection connection;
13 |
14 | // creating a connection with RabbitMQ
15 | private RPCServer() throws IOException, TimeoutException {
16 | super();
17 | connection = RPCBase.initConnection();
18 | }
19 |
20 | public static SingleServerChannel getSingleChannelExecutor(String queueName,
21 | TriFunction executor)
22 | throws IOException, TimeoutException {
23 | if (instance == null) {
24 | instance = new RPCServer();
25 | }
26 |
27 | return new SingleServerChannel(instance.connection.createChannel(), queueName, executor);
28 | }
29 |
30 | // Close the |channel| as to not waste resources.
31 | public void close() throws IOException {
32 | connection.close();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/apps/controller/src/main/java/org/sab/controller/ControllerClientHandler.java:
--------------------------------------------------------------------------------
1 | package org.sab.controller;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.ChannelHandler;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.SimpleChannelInboundHandler;
8 | import io.netty.util.CharsetUtil;
9 |
10 | @ChannelHandler.Sharable
11 | public class ControllerClientHandler extends SimpleChannelInboundHandler {
12 |
13 | private final String cmd;
14 |
15 | ControllerClientHandler(String cmd) {
16 | this.cmd = cmd;
17 | }
18 |
19 | @Override
20 | public void channelActive(ChannelHandlerContext ctx) {
21 | System.out.printf("Channel %s is active\n", ctx.channel().id());
22 | ctx.writeAndFlush(Unpooled.copiedBuffer(cmd, CharsetUtil.UTF_8));
23 |
24 |
25 | }
26 |
27 | @Override
28 | public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
29 | throw new UnsupportedOperationException("Controller shouldn't receive anything");
30 | }
31 |
32 | @Override
33 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
34 | cause.printStackTrace();
35 | ctx.close();
36 | }
37 | }
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/CreateGroupMessageRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class CreateGroupMessageRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "senderId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "senderId", "content"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 |
30 | UUID chatId = UUID.fromString((String) data.get("chatId"));
31 |
32 | ClientManager.broadcastResponseToChatChannels(chatId, response);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/CreateDirectMessageRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class CreateDirectMessageRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "senderId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "senderId", "content"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 |
30 | UUID chatId = UUID.fromString((String) data.get("chatId"));
31 |
32 | ClientManager.broadcastResponseToChatChannels(chatId, response);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/HttpServerUtilities/HttpClient.java:
--------------------------------------------------------------------------------
1 | package org.sab.HttpServerUtilities;
2 |
3 | import java.io.IOException;
4 | import java.net.URI;
5 | import java.net.http.HttpRequest;
6 | import java.net.http.HttpResponse;
7 |
8 | public class HttpClient {
9 | /**
10 | * Makes a getRequest to server deployed at localhost:8080
11 | *
12 | * @param uri assumes the uri is of the form api/appName (e.g api/user)
13 | */
14 | public static String get(String uri, String functionName) throws IOException, InterruptedException {
15 | return get(uri, functionName, 8080);
16 | }
17 |
18 | public static String get(String uri, String functionName, int port) throws IOException, InterruptedException {
19 | uri = String.format("http://localhost:%d/", port) + uri;
20 | java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
21 | HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
22 | .setHeader("Function-Name", functionName)
23 | .setHeader("Content-Type", "application/json")
24 | .build();
25 |
26 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
27 | return response.body();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/notification-client/public/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | // Scripts for firebase and firebase messaging
2 | importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js');
3 | importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js');
4 |
5 |
6 |
7 |
8 | // Initialize the Firebase app in the service worker by passing the generated config
9 | var firebaseConfig = {
10 | apiKey: "AIzaSyD0dQIsSM2S-ToVv3NP1hqahEO3Nfkjbeo",
11 | authDomain: "reddit-guc.firebaseapp.com",
12 | projectId: "reddit-guc",
13 | storageBucket: "reddit-guc.appspot.com",
14 | messagingSenderId: "1072681859465",
15 | appId: "1:1072681859465:web:8898b5300e2a82c313c958",
16 | measurementId: "G-W3Y9L5DVM6"
17 | };
18 |
19 | // eslint-disable-next-line no-undef
20 | firebase.initializeApp(firebaseConfig);
21 |
22 | // Retrieve firebase messaging
23 | // eslint-disable-next-line no-undef
24 | const messaging = firebase.messaging();
25 |
26 | messaging.onBackgroundMessage(function(payload) {
27 | console.log('Received background message ', payload);
28 |
29 | const notificationTitle = payload.notification.title;
30 | const notificationOptions = {
31 | body: payload.notification.body,
32 | };
33 |
34 | self.registration.showNotification(notificationTitle,
35 | notificationOptions);
36 | });
--------------------------------------------------------------------------------
/apps/user_app/src/main/java/org/sab/user/commands/ViewAnotherProfile.java:
--------------------------------------------------------------------------------
1 | package org.sab.user.commands;
2 |
3 | import org.sab.models.user.User;
4 | import org.sab.models.user.UserAttributes;
5 | import org.sab.service.Responder;
6 | import org.sab.service.validation.HTTPMethod;
7 | import org.sab.validation.Schema;
8 | import org.sab.validation.exceptions.EnvironmentVariableNotLoaded;
9 |
10 | import java.sql.SQLException;
11 |
12 | public class ViewAnotherProfile extends UserCommand {
13 | @Override
14 | protected String execute() {
15 | if (!uriParams.has(USERNAME))
16 | return Responder.makeErrorResponse("You must add username in URIParams!", 400);
17 | String username = uriParams.getString(USERNAME);
18 |
19 | try {
20 | User user = getUser(username, UserAttributes.USERNAME, UserAttributes.PHOTO_URL);
21 | return Responder.makeDataResponse(user.toJSON());
22 | } catch (SQLException | EnvironmentVariableNotLoaded e) {
23 | return Responder.makeErrorResponse(e.getMessage(), 400);
24 | }
25 |
26 | }
27 |
28 | @Override
29 | protected Schema getSchema() {
30 | return Schema.emptySchema();
31 | }
32 |
33 | @Override
34 | protected HTTPMethod getMethodType() {
35 | return HTTPMethod.GET;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reddit-cli",
3 | "version": "0.2.0",
4 | "license": "MIT",
5 | "bin": "cli.js",
6 | "engines": {
7 | "node": ">=10"
8 | },
9 | "scripts": {
10 | "start": "node cli.js",
11 | "test": "xo && ava"
12 | },
13 | "files": [
14 | "cli.js",
15 | "ui.js"
16 | ],
17 | "dependencies": {
18 | "import-jsx": "^4.0.0",
19 | "ink": "^3.0.8",
20 | "ink-big-text": "^1.2.0",
21 | "ink-gradient": "^2.0.0",
22 | "ink-select-input": "^4.2.0",
23 | "ink-spinner": "^4.0.1",
24 | "meow": "^9.0.0",
25 | "moment": "^2.29.1",
26 | "react": "^17.0.2",
27 | "websocket": "^1.0.34"
28 | },
29 | "devDependencies": {
30 | "@ava/babel": "^1.0.1",
31 | "@babel/preset-env": "^7.14.1",
32 | "@babel/preset-react": "^7.13.13",
33 | "@babel/register": "^7.13.16",
34 | "ava": "^3.15.0",
35 | "chalk": "^4.1.1",
36 | "eslint-config-xo-react": "^0.25.0",
37 | "eslint-plugin-react": "^7.23.2",
38 | "eslint-plugin-react-hooks": "^4.2.0",
39 | "ink-testing-library": "^2.1.0",
40 | "xo": "^0.39.1"
41 | },
42 | "ava": {
43 | "babel": true,
44 | "require": [
45 | "@babel/register"
46 | ]
47 | },
48 | "babel": {
49 | "presets": [
50 | "@babel/preset-env",
51 | "@babel/preset-react"
52 | ]
53 | },
54 | "xo": {
55 | "extends": "xo-react",
56 | "rules": {
57 | "react/prop-types": "off"
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/classes/ClassRegistry.java:
--------------------------------------------------------------------------------
1 | package org.sab.classes;
2 |
3 | import java.util.Map;
4 | import java.util.concurrent.ConcurrentHashMap;
5 |
6 | public class ClassRegistry {
7 | private static final ClassRegistry instance = new ClassRegistry();
8 | private final Map> classes = new ConcurrentHashMap<>();
9 |
10 | private ClassRegistry() {
11 | }
12 |
13 | public static ClassRegistry getInstance() {
14 | return instance;
15 | }
16 |
17 | public Class> getClass(String name) throws ClassNotFoundException {
18 | final Class> clazz = classes.get(name);
19 |
20 | if (clazz == null) {
21 | throw new ClassNotFoundException("Class not found in registry.");
22 | }
23 |
24 | return clazz;
25 | }
26 |
27 | public Class> addClassByName(String name) throws ClassNotFoundException {
28 | final Class clazz = ByteClassLoader.loadClassByName(name);
29 | classes.put(name, clazz);
30 | return clazz;
31 | }
32 |
33 | public Class> addClassByBytes(String name, byte[] b) {
34 | final Class clazz = ByteClassLoader.loadClassFromBytes(name, b);
35 | classes.put(name, clazz);
36 | return clazz;
37 | }
38 |
39 | public void removeClass(String name) {
40 | classes.remove(name);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/apps/user_app/src/main/java/org/sab/user/commands/ViewMyProfile.java:
--------------------------------------------------------------------------------
1 | package org.sab.user.commands;
2 |
3 | import org.sab.models.user.User;
4 | import org.sab.models.user.UserAttributes;
5 | import org.sab.service.Responder;
6 | import org.sab.service.validation.HTTPMethod;
7 | import org.sab.validation.Schema;
8 | import org.sab.validation.exceptions.EnvironmentVariableNotLoaded;
9 |
10 | import java.sql.SQLException;
11 |
12 | public class ViewMyProfile extends UserCommand {
13 | @Override
14 | protected String execute() {
15 | String username = authenticationParams.getString(USERNAME);
16 |
17 | try {
18 | User user = getUser(username, UserAttributes.USERNAME, UserAttributes.PASSWORD, UserAttributes.EMAIL, UserAttributes.BIRTHDATE, UserAttributes.PHOTO_URL, UserAttributes.USER_ID);
19 | return Responder.makeDataResponse(user.toJSON());
20 | } catch (SQLException | EnvironmentVariableNotLoaded e) {
21 | return Responder.makeErrorResponse(e.getMessage(), 500);
22 | }
23 | }
24 |
25 | @Override
26 | protected Schema getSchema() {
27 | return Schema.emptySchema();
28 | }
29 |
30 | @Override
31 | protected HTTPMethod getMethodType() {
32 | return HTTPMethod.GET;
33 | }
34 |
35 | @Override
36 | protected boolean isAuthNeeded() {
37 | return true;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/libs/utilities/src/main/java/org/sab/classes/Reader.java:
--------------------------------------------------------------------------------
1 | package org.sab.classes;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.net.URISyntaxException;
6 | import java.net.URL;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 | import java.nio.file.Paths;
10 |
11 | public class Reader {
12 | private Reader() {
13 | }
14 |
15 | public static byte[] getBytes(Path path) throws IOException {
16 | byte[] buffer;
17 |
18 | try (final InputStream inputStream = Files.newInputStream(path)) {
19 | buffer = inputStream.readAllBytes();
20 | }
21 |
22 | return buffer;
23 | }
24 |
25 | public static Path getResourcePath(String resource) {
26 | final URL url = Reader.class.getClassLoader().getResource(resource);
27 |
28 | if (url == null) {
29 | return null;
30 | }
31 |
32 | try {
33 | return Paths.get(url.toURI());
34 | } catch (URISyntaxException e) {
35 | return null;
36 | }
37 | }
38 |
39 | public static byte[] readBytesFromResource(String resource) throws IOException {
40 | final Path path = getResourcePath(resource);
41 | if(path == null) {
42 | throw new IOException("Path could not be found.");
43 | }
44 |
45 | return Reader.getBytes(path);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/AddMemberRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class AddMemberRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "adminId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "adminId", "memberId"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 | UUID chatId = UUID.fromString((String) data.get("chatId"));
30 | UUID targetMemberId = UUID.fromString((String) data.get("targetMemberId"));
31 | ClientManager.handleMemberAdded(chatId, targetMemberId);
32 |
33 | ClientManager.broadcastResponseToChatChannels(chatId, response);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/cli/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const React = require('react')
3 | const importJsx = require('import-jsx')
4 | const ChatContext = require('./contexts/chat-context')
5 | const { useState } = require('react')
6 | const { getAuthToken, mapSpecialIdToId } = require('./utils/id-mapper')
7 | const WelcomeFlow = importJsx('./flows/welcome-flow')
8 | const ChatFlow = importJsx('./flows/chat-flow')
9 |
10 | const AppContext = importJsx('./contexts/app-context')
11 |
12 | const defaultChatContext = {
13 | isLoadingChats: true,
14 | isLoadingMessages: true,
15 | highlightedChat: null,
16 | directChats: [],
17 | groupChats: [],
18 | selectedChat: null,
19 | messages: {},
20 | sendToChat: null
21 | }
22 |
23 | const App = ({ command = 'welcome', flags }) => {
24 | const [chatContext, setChatContext] = useState(defaultChatContext)
25 |
26 | const { rainbow, user = '6b57562b-3667-426c-ae6a-372c8ea6ff91' } = flags
27 |
28 | const defaultAppContext = {
29 | authToken: getAuthToken(user),
30 | userId: mapSpecialIdToId(user)
31 | }
32 |
33 | return (
34 |
35 | {command.toLowerCase() === 'welcome' && }
36 |
37 | {command.toLowerCase() === 'chat' && }
38 |
39 |
40 | )
41 | }
42 |
43 | module.exports = App
44 |
--------------------------------------------------------------------------------
/cli/components/create-group-chat.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text, Box, useInput } = require('ink')
3 | const { useContext } = require('react')
4 | const ChatContext = require('../contexts/chat-context')
5 | const importJsx = require('import-jsx')
6 | const AppContext = require('../contexts/app-context')
7 | const TextInput = importJsx('./text-input')
8 |
9 | const { useState } = React
10 |
11 | const CreateGroupChat = ({ onBack }) => {
12 | const { authToken } = useContext(AppContext)
13 | const [chatContext, _] = useContext(ChatContext)
14 | const [chatName, setChatName] = useState('')
15 |
16 | const onCreate = (chatName) => {
17 | chatContext.sendToChat({
18 | type: 'CREATE_GROUP_CHAT',
19 | authToken,
20 | name: chatName,
21 | description: ''
22 | })
23 | onBack()
24 | }
25 |
26 | useInput((input, _) => {
27 | if (input.charCodeAt(0) === 60) {
28 | onBack()
29 | }
30 | })
31 |
32 | return (
33 |
34 | Create a Group Chat
35 |
36 |
37 | {'>'}
38 |
39 |
45 |
46 | {`\nPress “<” to go back`}
47 |
48 | )
49 | }
50 |
51 | module.exports = CreateGroupChat
52 |
--------------------------------------------------------------------------------
/apps/notification_app/src/test/java/org/sab/notification/commands/SendNotificationTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification.commands;
2 |
3 | import org.json.JSONObject;
4 | import org.junit.Ignore;
5 | import org.junit.Test;
6 | import org.sab.service.validation.HTTPMethod;
7 | import org.sab.tests.TestsUtils;
8 |
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import static org.junit.Assert.assertTrue;
13 |
14 | @Ignore
15 | public class SendNotificationTest {
16 |
17 | private static final String USERNAME = "scale-a-bull";
18 |
19 | @Test
20 | public void shouldSendSingularWithSuccess() {
21 | final JSONObject body = new JSONObject(
22 | Map.of(
23 | "usersList", List.of(USERNAME),
24 | "title", "Some Title",
25 | "notificationBody", "Lorem ipsum"
26 | )
27 | );
28 | JSONObject request = TestsUtils.makeAuthorizedRequest(body, HTTPMethod.PUT.toString(), new JSONObject());
29 | final JSONObject response = new JSONObject(new SendNotification().execute(request));
30 | String notificationResult = response.getJSONObject("data").getString("notificationResult");
31 | final boolean isSuccessful = notificationResult.startsWith("Successfully sent message");
32 | assertTrue("Should send successfully", isSuccessful);
33 | }
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/cli/flows/chat-flow.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const React = require('react')
3 | const importJsx = require('import-jsx')
4 | const ChatContext = require('../contexts/chat-context')
5 | const { useEffect, useContext } = require('react')
6 | const useChatService = require('../hooks/chat-service')
7 | const LoadingSpinner = importJsx('../components/loading-spinner')
8 | const ChatsList = importJsx('../components/chats-list')
9 | const ChatView = importJsx('../components/chat-view')
10 |
11 | const ChatFlow = () => {
12 | const [chatContext, setChatContext] = useContext(ChatContext)
13 |
14 | const sendToChat = useChatService()
15 |
16 | useEffect(() => setChatContext({ ...chatContext, sendToChat }), [])
17 |
18 | const onChatSelect = (chat) => {
19 | setChatContext({
20 | ...chatContext,
21 | selectedChat: chat,
22 | isLoadingMessages: true
23 | })
24 | }
25 | const onChatExit = () => {
26 | setChatContext({ ...chatContext, selectedChat: null })
27 | }
28 |
29 | return (
30 |
31 | {!chatContext.isLoadingChats ? (
32 |
33 | {chatContext.selectedChat ? (
34 |
35 | ) : (
36 |
37 | )}
38 |
39 | ) : (
40 |
41 | )}
42 |
43 | )
44 | }
45 |
46 | module.exports = ChatFlow
47 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/RemoveMemberRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class RemoveMemberRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "adminId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "adminId", "memberId"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 | UUID chatId = UUID.fromString((String) data.get("chatId"));
30 | UUID targetMemberId = UUID.fromString((String) data.get("targetMemberId"));
31 |
32 | ClientManager.broadcastResponseToChatChannels(chatId, response);
33 |
34 | ClientManager.handleMemberRemoved(chatId, targetMemberId);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/apps/example_app/src/main/java/org/sab/demo/commands/HelloArango.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo.commands;
2 |
3 | import com.arangodb.entity.BaseDocument;
4 | import org.sab.arango.Arango;
5 | import org.sab.service.validation.CommandWithVerification;
6 | import org.sab.service.validation.HTTPMethod;
7 | import org.sab.validation.Schema;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 |
13 | public class HelloArango extends CommandWithVerification {
14 |
15 | @Override
16 | protected String execute() {
17 | // Return SUCCESS
18 | String DB_NAME = System.getenv("ARANGO_DB");
19 | String COLLECTION_NAME = "Hello";
20 |
21 | Arango arango = Arango.getInstance();
22 |
23 | arango.createCollectionIfNotExists( DB_NAME, COLLECTION_NAME, false);
24 |
25 | Map properties = new HashMap<>();
26 | properties.put("hello", true);
27 | properties.put("bye", "not bye");
28 | BaseDocument helloDoc = new BaseDocument(properties);
29 |
30 | arango.createDocument(DB_NAME, COLLECTION_NAME, helloDoc);
31 |
32 | return "{\"msg\":\"Successfully inserted Hello\", \"statusCode\": 200}";
33 | }
34 |
35 | @Override
36 | protected Schema getSchema() {
37 | return Schema.emptySchema();
38 | }
39 |
40 | @Override
41 | protected HTTPMethod getMethodType() {
42 | return HTTPMethod.POST;
43 | }
44 | }
--------------------------------------------------------------------------------
/libs/models/src/main/java/org/sab/models/CollectionNames.java:
--------------------------------------------------------------------------------
1 | package org.sab.models;
2 |
3 | public enum CollectionNames {
4 |
5 | // model collections
6 | THREAD("Thread"),
7 | SUBTHREAD("Subthread"),
8 | COMMENT("Comment"),
9 | USER("User"),
10 | // # Subthread collections
11 | USER_CREATE_SUBTHREAD("UserCreateSubthread"),
12 | USER_BOOKMARK_SUBTHREAD("UserBookmarkSubthread"),
13 | USER_LIKE_SUBTHREAD("UserLikeSubthread"),
14 | USER_DISLIKE_SUBTHREAD("UserDislikeSubthread"),
15 | SUBTHREAD_REPORTS("SubthreadReports"),
16 | USER_FOLLOW_USER("UserFollowUser"),
17 | USER_MOD_THREAD("UserModThread"),
18 | // Comments collections
19 | USER_CREATE_COMMENT("UserCreateComment"),
20 | CONTENT_COMMENT("ContentComment"),
21 | USER_LIKE_COMMENT("UserLikeComment"),
22 | USER_DISLIKE_COMMENT("UserDislikeComment"),
23 | // Thread Collection
24 | USER_FOLLOW_THREAD("UserFollowThread"),
25 | USER_BOOKMARK_THREAD("UserBookmarkThread"),
26 | THREAD_CONTAIN_SUBTHREAD("ThreadContainSubThread"),
27 | USER_BANNED_FROM_THREAD("UserBannedFromThread"),
28 | USER_BLOCK_USER("UserBlockUser");
29 |
30 |
31 | private final String collectionName;
32 |
33 | CollectionNames(String collectionName) {
34 | this.collectionName = collectionName;
35 | }
36 |
37 | public String get() {
38 | return this.collectionName;
39 | }
40 |
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/apps/user_app/src/main/java/org/sab/user/UserApp.java:
--------------------------------------------------------------------------------
1 | package org.sab.user;
2 |
3 |
4 | import org.sab.arango.Arango;
5 | import org.sab.functions.Utilities;
6 | import org.sab.models.CollectionNames;
7 | import org.sab.postgres.PostgresConnection;
8 | import org.sab.service.Service;
9 | import org.sab.validation.exceptions.EnvironmentVariableNotLoaded;
10 |
11 | import java.io.IOException;
12 |
13 | public class UserApp extends Service {
14 | public static final String ARANGO_DB_NAME = System.getenv("ARANGO_DB");
15 |
16 | public static void main(String[] args) throws IOException, EnvironmentVariableNotLoaded {
17 |
18 | new UserApp().start();
19 | dbInit();
20 | }
21 |
22 | public static void dbInit() throws IOException, EnvironmentVariableNotLoaded {
23 | if (!Utilities.inContainerizationMode())
24 | PostgresConnection.dbInit();
25 | arangoDbInit();
26 | }
27 |
28 | private static void arangoDbInit() throws EnvironmentVariableNotLoaded {
29 | if (ARANGO_DB_NAME == null)
30 | throw new EnvironmentVariableNotLoaded("ARANGO_DB");
31 | Arango arango = Arango.getInstance();
32 | arango.createDatabaseIfNotExists(ARANGO_DB_NAME);
33 | arango.createCollectionIfNotExists(ARANGO_DB_NAME, CollectionNames.USER.get(), false);
34 | }
35 |
36 | @Override
37 | public String getAppUriName() {
38 | return "user";
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/apps/example_app/src/test/java/org/sab/demo/CommandNeedingAuthTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.demo;
2 |
3 | import org.json.JSONObject;
4 | import org.junit.Test;
5 | import org.sab.auth.AuthParamsHandler;
6 | import org.sab.demo.commands.CommandNeedingAuth;
7 |
8 | import static org.junit.Assert.assertEquals;
9 | import static org.junit.Assert.assertTrue;
10 |
11 | public class CommandNeedingAuthTest {
12 |
13 | @Test
14 | public void willFailDueToUnAuthentication() {
15 | JSONObject request = new JSONObject().put("uriParams", new JSONObject()).put("methodType", "GET");
16 | AuthParamsHandler.putUnauthorizedParams(request);
17 | JSONObject response = new JSONObject(new CommandNeedingAuth().execute(request));
18 | int statusCode = response.getInt("statusCode");
19 | assertTrue(statusCode == 401 || statusCode == 403);
20 | assertTrue(response.getString("msg").startsWith("Unauthorized"));
21 | }
22 |
23 | @Test
24 | public void willSucceedAfterAuthentication() {
25 | JSONObject request = new JSONObject().put("uriParams", new JSONObject()).put("methodType", "GET");
26 | AuthParamsHandler.putAuthorizedParams(request);
27 | JSONObject response = new JSONObject(new CommandNeedingAuth().execute(request));
28 | assertEquals(200, response.getInt("statusCode"));
29 | assertEquals("Authentication successful!", response.getString("msg"));
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/cli/components/create-direct-chat.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text, Box, useInput } = require('ink')
3 | const { useContext } = require('react')
4 | const ChatContext = require('../contexts/chat-context')
5 | const { mapSpecialIdToId } = require('../utils/id-mapper')
6 | const importJsx = require('import-jsx')
7 | const AppContext = require('../contexts/app-context')
8 | const TextInput = importJsx('./text-input')
9 |
10 | const { useState } = React
11 |
12 | const CreateDirectChat = ({ onBack }) => {
13 | const { authToken } = useContext(AppContext)
14 | const [chatContext, _] = useContext(ChatContext)
15 | const [otherUserId, setOtherUserId] = useState('')
16 |
17 | const onCreate = (targetUserId) => {
18 | chatContext.sendToChat({
19 | type: 'CREATE_DIRECT_CHAT',
20 | authToken,
21 | secondMember: mapSpecialIdToId(targetUserId)
22 | })
23 | onBack()
24 | }
25 |
26 | useInput((input, _) => {
27 | if (input.charCodeAt(0) === 60) {
28 | onBack()
29 | }
30 | })
31 |
32 | return (
33 |
34 | Create a DM
35 |
36 |
37 | {'>'}
38 |
39 |
45 |
46 | {`\nPress “<” to go back`}
47 |
48 | )
49 | }
50 |
51 | module.exports = CreateDirectChat
52 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/CreateGroupChatRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.UUID;
10 |
11 | public class CreateGroupChatRouter extends Router {
12 |
13 | @Override
14 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
15 | boolean isAuthenticated = authenticate(request, "creator");
16 | if (!isAuthenticated) {
17 | rejectUnAuthenticatedRequest(ctx);
18 | return;
19 | }
20 | String[] attributes = {"creator", "name", "description"};
21 | packAndForwardRequest(ctx, request, attributes);
22 | }
23 |
24 | @Override
25 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
26 | JSONObject data = (JSONObject) response.get("data");
27 | if(data == null) {
28 | handleError(ctx, response);
29 | return;
30 | }
31 |
32 | UUID chatId = UUID.fromString((String) data.get("chatId"));
33 |
34 | List memberIds = new ArrayList<>();
35 | memberIds.add(UUID.fromString((String) data.get("adminId")));
36 |
37 | ClientManager.handleUserCreateChat(chatId, memberIds);
38 |
39 | ClientManager.broadcastResponseToChatChannels(chatId, response);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_server/src/main/java/org/sab/chat/server/routers/LeaveGroupRouter.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.server.routers;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import org.json.simple.JSONObject;
5 | import org.sab.chat.server.ClientManager;
6 |
7 | import java.util.UUID;
8 |
9 | public class LeaveGroupRouter extends Router {
10 |
11 | @Override
12 | public void forwardRequestToQueue(ChannelHandlerContext ctx, JSONObject request) {
13 | boolean isAuthenticated = authenticate(request, "userId");
14 | if (!isAuthenticated) {
15 | rejectUnAuthenticatedRequest(ctx);
16 | return;
17 | }
18 | String[] attributes = {"chatId", "userId"};
19 | packAndForwardRequest(ctx, request, attributes);
20 | }
21 |
22 | @Override
23 | public void routeResponse(ChannelHandlerContext ctx, JSONObject response) {
24 | JSONObject data = (JSONObject) response.get("data");
25 | if(data == null) {
26 | handleError(ctx, response);
27 | return;
28 | }
29 | UUID chatId = UUID.fromString((String) data.get("chatId"));
30 | UUID targetMemberId = UUID.fromString((String) data.get("targetMemberId"));
31 | UUID adminId = UUID.fromString((String) data.get("adminId"));
32 |
33 | ClientManager.broadcastResponseToChatChannels(chatId, response);
34 |
35 | boolean isAdmin = targetMemberId.equals(adminId);
36 | ClientManager.handleUserLeftGroup(chatId, targetMemberId, isAdmin);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cli/components/add-group-member.js:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Text, Box, useInput } = require('ink')
3 | const { useContext } = require('react')
4 | const ChatContext = require('../contexts/chat-context')
5 | const importJsx = require('import-jsx')
6 | const AppContext = require('../contexts/app-context')
7 | const { mapSpecialIdToId } = require('../utils/id-mapper')
8 | const TextInput = importJsx('./text-input')
9 |
10 | const { useState } = React
11 |
12 | const AddGroupMember = ({ onBack }) => {
13 | const { authToken } = useContext(AppContext)
14 | const [chatContext, _] = useContext(ChatContext)
15 | const [memberId, setMemberId] = useState('')
16 |
17 | const chat = chatContext.highlightedChat
18 |
19 | const onAction = (memberId) => {
20 | chatContext.sendToChat({
21 | authToken,
22 | type: 'ADD_GROUP_MEMBER',
23 | chatId: chat.chatId,
24 | memberId: mapSpecialIdToId(memberId)
25 | })
26 | onBack()
27 | }
28 |
29 | useInput((input, _) => {
30 | if (input.charCodeAt(0) === 60) {
31 | onBack()
32 | }
33 | })
34 |
35 | return (
36 |
37 | {`Add a new member to ${chat.name}`}
38 |
39 |
40 | {'>'}
41 |
42 |
48 |
49 | {`\nPress “<” to go back`}
50 |
51 | )
52 | }
53 |
54 | module.exports = AddGroupMember
55 |
--------------------------------------------------------------------------------
/apps/chat_app/chat_storage/src/test/java/org/sab/chat/storage/config/CassandraConnectorTest.java:
--------------------------------------------------------------------------------
1 | package org.sab.chat.storage.config;
2 |
3 | import com.datastax.driver.core.ResultSet;
4 | import org.junit.After;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.sab.chat.storage.config.CassandraConnector;
8 | import org.sab.databases.PoolDoesNotExistException;
9 |
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | import static org.junit.Assert.assertEquals;
14 |
15 | public class CassandraConnectorTest {
16 | private CassandraConnector cassandra;
17 |
18 | @Before
19 | public void connect() {
20 | cassandra = CassandraConnector.getConnectedInstance();
21 | }
22 |
23 | @After
24 | public void disconnect() throws PoolDoesNotExistException {
25 | cassandra.destroyPool();
26 | }
27 |
28 | @Test
29 | public void whenCreatingKeyspace_thenCreated() {
30 | String keyspaceName = cassandra.getKeyspaceName();
31 |
32 | ResultSet result =
33 | cassandra.runQuery("SELECT * FROM system_schema.keyspaces;");
34 |
35 | List matchedKeyspaces = result.all()
36 | .stream()
37 | .filter(row -> row.getString(0).equals(keyspaceName.toLowerCase()))
38 | .map(row -> row.getString(0))
39 | .collect(Collectors.toList());
40 |
41 | assertEquals(1, matchedKeyspaces.size());
42 | assertEquals(matchedKeyspaces.get(0), keyspaceName.toLowerCase());
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/apps/subthread_app/src/main/resources/commandmap.properties:
--------------------------------------------------------------------------------
1 | ADD_IMAGE_TO_SUBTHREAD=org.sab.subthread.commands.AddImageToSubThread
2 | BOOKMARK_SUBTHREAD=org.sab.subthread.commands.BookmarkSubThread
3 | CREATE_COMMENT=org.sab.subthread.commands.CreateComment
4 | CREATE_SUBTHREAD=org.sab.subthread.commands.CreateSubThread
5 | DELETE_COMMENT=org.sab.subthread.commands.DeleteComment
6 | DELETE_SUBTHREAD=org.sab.subthread.commands.DeleteSubThread
7 | DISLIKE_COMMENT=org.sab.subthread.commands.DislikeComment
8 | DISLIKE_SUBTHREAD=org.sab.subthread.commands.DislikeSubThread
9 | GET_COMMENT=org.sab.subthread.commands.GetComment
10 | GET_COMMENTS=org.sab.subthread.commands.GetComments
11 | GET_MY_COMMENTS=org.sab.subthread.commands.GetMyComments
12 | GET_MY_DISLIKED_COMMENTS=org.sab.subthread.commands.GetMyDislikedComments
13 | GET_MY_DISLIKED_SUBTHREADS=org.sab.subthread.commands.GetMyDislikedSubThreads
14 | GET_MY_LIKED_COMMENTS=org.sab.subthread.commands.GetMyLikedComments
15 | GET_MY_LIKED_SUBTHREADS=org.sab.subthread.commands.GetMyLikedSubThreads
16 | GET_MY_SUBTHREADS=org.sab.subthread.commands.GetMySubThreads
17 | GET_SUBTHREAD=org.sab.subthread.commands.GetSubThread
18 | GET_SUBTHREADS=org.sab.subthread.commands.GetSubThreads
19 | LIKE_COMMENT=org.sab.subthread.commands.LikeComment
20 | LIKE_SUBTHREAD=org.sab.subthread.commands.LikeSubThread
21 | MODERATOR_SEE_REPORTS=org.sab.subthread.commands.ModeratorSeeReports
22 | REPORT_SUBTHREAD=org.sab.subthread.commands.ReportSubThread
23 | UPDATE_COMMENT=org.sab.subthread.commands.UpdateComment
24 | UPDATE_SUBTHREAD=org.sab.subthread.commands.UpdateSubThread
25 |
--------------------------------------------------------------------------------
/apps/recommendation_app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | rabbitmq:
4 | image: "rabbitmq:management"
5 | ports:
6 | - 15672:15672
7 | arangodb:
8 | image: "arangodb"
9 | ports:
10 | - 8529:8529
11 | volumes:
12 | - arangodb_data:/var/lib/arangodb3
13 | - arangodb_apps_data:/var/lib/arangodb3-apps
14 | environment:
15 | - ARANGO_ROOT_PASSWORD=root
16 | couchbase:
17 | build:
18 | context: "../../"
19 | dockerfile: "libs/couchbase/Dockerfile"
20 | environment:
21 | - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator
22 | - COUCHBASE_ADMINISTRATOR_PASSWORD=password
23 | - COUCHBASE_CLUSTER_NAME=Reddit
24 | - COUCHBASE_RAM=1024
25 | ports:
26 | - 8091-8094:8091-8094
27 | - 11210:11210
28 | volumes:
29 | - cb_data:/opt/couchbase/var
30 | recommendation_app:
31 | build:
32 | context: "../../"
33 | dockerfile: "apps/recommendation_app/Dockerfile"
34 | restart: on-failure
35 | ports:
36 | - "4004:4004"
37 | environment:
38 | - RABBIT_HOST=rabbitmq
39 | - ARANGO_DB=Reddit
40 | - ARANGO_USER=root
41 | - ARANGO_PASSWORD=root
42 | - ARANGO_HOST=arangodb
43 | - COUCHBASE_HOST=couchbase
44 | - COUCHBASE_USERNAME=Administrator
45 | - COUCHBASE_PASSWORD=password
46 | depends_on:
47 | - "rabbitmq"
48 | - "arangodb"
49 | - "couchbase"
50 |
51 | networks:
52 | default:
53 | name: recommendation_nw
54 |
55 | volumes:
56 | cb_data:
57 | arangodb_data:
58 | arangodb_apps_data:
59 |
--------------------------------------------------------------------------------
/apps/notification_app/src/main/java/org/sab/notification/commands/GetNotifications.java:
--------------------------------------------------------------------------------
1 | package org.sab.notification.commands;
2 |
3 | import com.google.cloud.firestore.CollectionReference;
4 | import org.json.JSONArray;
5 | import org.sab.models.user.UserAttributes;
6 | import org.sab.notification.FirestoreConnector;
7 | import org.sab.notification.NotificationApp;
8 | import org.sab.service.Responder;
9 | import org.sab.service.validation.CommandWithVerification;
10 | import org.sab.service.validation.HTTPMethod;
11 | import org.sab.validation.Schema;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Map;
16 | import java.util.concurrent.ExecutionException;
17 |
18 | public class GetNotifications extends CommandWithVerification {
19 |
20 | @Override
21 | protected String execute() {
22 | String username = authenticationParams.getString(UserAttributes.USERNAME.toString());
23 | FirestoreConnector firestoreConnector = FirestoreConnector.getInstance();
24 |
25 | List