├── src
└── main
│ └── java
│ └── com
│ └── joinhocus
│ └── horus
│ ├── http
│ ├── model
│ │ └── EmptyRequest.java
│ ├── routes
│ │ ├── invites
│ │ │ ├── InviteRequest.java
│ │ │ ├── create
│ │ │ │ ├── model
│ │ │ │ │ └── CreateInviteRequest.java
│ │ │ │ └── CreateInviteHandler.java
│ │ │ ├── get
│ │ │ │ └── GetCreatedInvitesHandler.java
│ │ │ ├── validate
│ │ │ │ └── ValidateInviteHandler.java
│ │ │ └── token
│ │ │ │ └── SendVerificationEmailHandler.java
│ │ ├── account
│ │ │ ├── code
│ │ │ │ ├── model
│ │ │ │ │ └── InviteCodeRequest.java
│ │ │ │ └── ValidateInviteCodeHandler.java
│ │ │ ├── setup
│ │ │ │ ├── model
│ │ │ │ │ └── SetupAccountRequest.java
│ │ │ │ ├── SetupAccountHandler.java
│ │ │ │ └── GetUserInviterInfo.java
│ │ │ ├── login
│ │ │ │ ├── model
│ │ │ │ │ └── AccountLoginRequest.java
│ │ │ │ └── AccountLoginHandler.java
│ │ │ ├── create
│ │ │ │ ├── model
│ │ │ │ │ ├── VerifyComboRequest.java
│ │ │ │ │ └── CreateAccountRequest.java
│ │ │ │ └── ValidateInviteTwitterComboHandler.java
│ │ │ ├── forgotpw
│ │ │ │ ├── model
│ │ │ │ │ ├── ForgotPasswordResetRequest.java
│ │ │ │ │ └── ForgotPasswordTokenRequest.java
│ │ │ │ ├── RequestPasswordResetHandler.java
│ │ │ │ └── ResetPasswordHandler.java
│ │ │ ├── changepw
│ │ │ │ ├── model
│ │ │ │ │ └── ChangePasswordRequest.java
│ │ │ │ └── ChangePasswordHandler.java
│ │ │ ├── get
│ │ │ │ ├── CheckAccountUsernameAvailabilityHandler.java
│ │ │ │ └── GetAccountHandler.java
│ │ │ └── settings
│ │ │ │ └── UpdateAccountAvatarHandler.java
│ │ ├── posts
│ │ │ ├── like
│ │ │ │ ├── model
│ │ │ │ │ └── LikePostRequest.java
│ │ │ │ └── LikePostHandler.java
│ │ │ ├── model
│ │ │ │ ├── ReplyToPostRequest.java
│ │ │ │ └── CreatePostRequest.java
│ │ │ ├── get
│ │ │ │ ├── model
│ │ │ │ │ ├── AuthorFilter.java
│ │ │ │ │ └── GetFeedRequest.java
│ │ │ │ └── GetPostByIdHandler.java
│ │ │ └── CreatePostHandler.java
│ │ ├── organization
│ │ │ ├── create
│ │ │ │ ├── model
│ │ │ │ │ └── CreateOrganizationRequest.java
│ │ │ │ └── CreateOrganizationHandler.java
│ │ │ ├── getbyuser
│ │ │ │ └── GetUserOrganizationsHandler.java
│ │ │ └── settings
│ │ │ │ └── logo
│ │ │ │ └── UpdateOrganizationLogoHandler.java
│ │ ├── waitlist
│ │ │ ├── validate
│ │ │ │ ├── model
│ │ │ │ │ ├── EmailValidateWaitListRequest.java
│ │ │ │ │ └── TwitterValidateWaitListRequest.java
│ │ │ │ ├── EmailValidateWaitlistHandler.java
│ │ │ │ └── TwitterValidateWaitListHandler.java
│ │ │ ├── verify
│ │ │ │ ├── model
│ │ │ │ │ └── VerifyTwitterOAuthRequest.java
│ │ │ │ └── VerifyWaitlistTwitterHandler.java
│ │ │ ├── join
│ │ │ │ └── JoinWaitlistTwitterHandler.java
│ │ │ ├── register
│ │ │ │ └── twitter
│ │ │ │ │ └── RegisterWithTwitterHandler.java
│ │ │ └── WaitlistUtil.java
│ │ ├── entity
│ │ │ ├── model
│ │ │ │ └── FollowEntityRequest.java
│ │ │ ├── SearchByHandleHandler.java
│ │ │ ├── GetFollowsEntityHandler.java
│ │ │ └── FollowEntityHandler.java
│ │ ├── twitter
│ │ │ └── info
│ │ │ │ └── TwitterInfoHandler.java
│ │ └── activity
│ │ │ ├── GetActivityCountHandler.java
│ │ │ └── GetActivityFeedHandler.java
│ ├── DefinedTypesWithUserHandler.java
│ ├── Response.java
│ └── DefinedTypesHandler.java
│ ├── post
│ ├── PostPrivacy.java
│ ├── PostType.java
│ ├── Post.java
│ ├── PostUtil.java
│ └── impl
│ │ ├── BasicPost.java
│ │ └── PostWithExtra.java
│ ├── account
│ ├── AccountType.java
│ ├── activity
│ │ ├── NotificationType.java
│ │ └── ActivityNotification.java
│ ├── UserNames.java
│ ├── AccountStatus.java
│ ├── invite
│ │ └── Invite.java
│ ├── AccountAuth.java
│ └── UserAccount.java
│ ├── misc
│ ├── follow
│ │ ├── EntityType.java
│ │ └── Followable.java
│ ├── strgen
│ │ └── RandomStringGenerator.java
│ ├── function
│ │ ├── TriFunction.java
│ │ └── QuadFunction.java
│ ├── PaginatedList.java
│ ├── Pair.java
│ ├── MongoIds.java
│ ├── IOUtils.java
│ ├── email
│ │ └── EmailClient.java
│ ├── gson
│ │ └── ObjectIdSerializer.java
│ ├── Environment.java
│ ├── HandlesUtil.java
│ └── Spaces.java
│ ├── organization
│ ├── OrganizationRole.java
│ ├── OrganizationSettings.java
│ ├── Organization.java
│ └── SimpleOrganization.java
│ ├── Main.java
│ ├── slack
│ ├── SlackClientExtension.java
│ ├── horris
│ │ └── BasicHorrisExtension.java
│ ├── SlackEventsHandler.java
│ ├── waitlist
│ │ └── SlackWaitListExtension.java
│ └── SlackClient.java
│ ├── twitter
│ ├── oauth
│ │ ├── OAuthResponseValues.java
│ │ ├── OAuthAccountResponse.java
│ │ └── TwitterOauth.java
│ ├── data
│ │ └── TwitterUser.java
│ ├── TwitterAPI.java
│ └── TwitterOAuthHeaderGenerator.java
│ ├── config
│ ├── Config.java
│ └── Configs.java
│ └── db
│ ├── repos
│ ├── EmailInviteVerificationRepo.java
│ ├── LikesRepo.java
│ ├── PasswordResetRepo.java
│ ├── InvitesRepo.java
│ ├── WaitListRepo.java
│ ├── FollowsRepo.java
│ ├── ActivityRepo.java
│ └── OrganizationRepo.java
│ ├── config
│ └── MongoConfig.java
│ └── MongoDatabase.java
├── README.md
├── Dockerfile
├── .gitignore
└── pom.xml
/src/main/java/com/joinhocus/horus/http/model/EmptyRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.model;
2 |
3 | public class EmptyRequest {
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/post/PostPrivacy.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.post;
2 |
3 | public enum PostPrivacy {
4 | PUBLIC,
5 | PRIVATE
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/AccountType.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account;
2 |
3 | public enum AccountType {
4 | INVESTOR,
5 | FOUNDER,
6 | OTHER;
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/follow/EntityType.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.follow;
2 |
3 | public enum EntityType {
4 | ACCOUNT,
5 | ORGANIZATION
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/organization/OrganizationRole.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.organization;
2 |
3 | public enum OrganizationRole {
4 | CREATOR,
5 | MEMBER
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/Main.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus;
2 |
3 | public class Main {
4 |
5 | public static void main(String[] args) {
6 | new Horus();
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/strgen/RandomStringGenerator.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.strgen;
2 |
3 | public interface RandomStringGenerator {
4 |
5 | String generate(int length);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/follow/Followable.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.follow;
2 |
3 | import org.bson.types.ObjectId;
4 |
5 | public interface Followable {
6 | ObjectId getId();
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/activity/NotificationType.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account.activity;
2 |
3 | public enum NotificationType {
4 | FOLLOW,
5 | REPLY,
6 | MENTION,
7 | LIKE
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/function/TriFunction.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.function;
2 |
3 | @FunctionalInterface
4 | public interface TriFunction {
5 |
6 | R apply(A a, B b, C c);
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/slack/SlackClientExtension.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.slack;
2 |
3 | import com.slack.api.bolt.App;
4 |
5 | public interface SlackClientExtension {
6 |
7 | void register(App app);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/function/QuadFunction.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.function;
2 |
3 | @FunctionalInterface
4 | public interface QuadFunction {
5 |
6 | R apply(A a, B b, C c, D d);
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/invites/InviteRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.invites;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class InviteRequest {
7 |
8 | private final String inviteId;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/twitter/oauth/OAuthResponseValues.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.twitter.oauth;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class OAuthResponseValues {
7 |
8 | private final String token, secret;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/twitter/oauth/OAuthAccountResponse.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.twitter.oauth;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class OAuthAccountResponse {
7 |
8 | private final String userId, name;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/code/model/InviteCodeRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.code.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class InviteCodeRequest {
7 |
8 | private final String code;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/like/model/LikePostRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.like.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class LikePostRequest {
7 |
8 | private final String id;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Horus
2 |
3 | A heavily mongodb-reliant platform for investors to connect to their founders.
4 | Data aggregation pipelines were heavily optimized but are quite unreadable.
5 |
6 | This project was abandoned but the foundations remain strong and a lot can be stripped away and re-used.
7 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/setup/model/SetupAccountRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.setup.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class SetupAccountRequest {
7 |
8 | private final String selected;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/login/model/AccountLoginRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.login.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class AccountLoginRequest {
7 |
8 | private final String login, password;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/organization/create/model/CreateOrganizationRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.organization.create.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class CreateOrganizationRequest {
7 | private final String name, handle;
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/post/PostType.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.post;
2 |
3 | import java.util.EnumSet;
4 |
5 | public enum PostType {
6 | UPDATE,
7 | WIN,
8 | ASK;
9 |
10 | public static EnumSet ALL = EnumSet.allOf(PostType.class);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/create/model/VerifyComboRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.create.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class VerifyComboRequest {
7 |
8 | private final String inviteCode, twitterId;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/forgotpw/model/ForgotPasswordResetRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.forgotpw.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class ForgotPasswordResetRequest {
7 |
8 | private final String login;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/validate/model/EmailValidateWaitListRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.validate.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class EmailValidateWaitListRequest {
7 |
8 | private final String code;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/verify/model/VerifyTwitterOAuthRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.verify.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class VerifyTwitterOAuthRequest {
7 |
8 | private final String token, verify;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/changepw/model/ChangePasswordRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.changepw.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class ChangePasswordRequest {
7 |
8 | private final String current, changeTo;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/model/ReplyToPostRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class ReplyToPostRequest {
7 |
8 | private final String content;
9 | private final String parent;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:11-jdk
2 |
3 | RUN mkdir /app
4 | WORKDIR /app
5 | COPY target/hocus-1.0-SNAPSHOT.jar hocus.jar
6 |
7 | RUN wget -O /bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
8 | RUN chmod +x /bin/dumb-init
9 | ENTRYPOINT ["/bin/dumb-init", "--"]
10 |
11 | CMD java -jar hocus.jar
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/validate/model/TwitterValidateWaitListRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.validate.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class TwitterValidateWaitListRequest {
7 |
8 | private final String twitterId, verify, token;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/forgotpw/model/ForgotPasswordTokenRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.forgotpw.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class ForgotPasswordTokenRequest {
7 |
8 | private final String token;
9 | private final String password;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/get/model/AuthorFilter.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.get.model;
2 |
3 | import lombok.Data;
4 | import org.bson.types.ObjectId;
5 |
6 | @Data
7 | public class AuthorFilter {
8 | private final ObjectId author;
9 | private final ObjectId organization;
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/PaginatedList.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import com.google.gson.JsonObject;
4 | import lombok.Data;
5 |
6 | import java.util.List;
7 |
8 | @Data
9 | public class PaginatedList {
10 | private final JsonObject paginationData;
11 | private final List data;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/UserNames.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.regex.Pattern;
6 |
7 | @Data
8 | public class UserNames {
9 |
10 | public static final Pattern NAME_PATTERN = Pattern.compile("^(\\w){1,15}$");
11 |
12 | private final String name, display;
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/entity/model/FollowEntityRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.entity.model;
2 |
3 | import com.joinhocus.horus.misc.follow.EntityType;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class FollowEntityRequest {
8 | private final String id;
9 | private final EntityType type;
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/invites/create/model/CreateInviteRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.invites.create.model;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class CreateInviteRequest {
7 |
8 | private final String sendTo;
9 | private final String orgId;
10 | private final boolean handleDeliver;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/create/model/CreateAccountRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.create.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | @AllArgsConstructor
7 | @Getter
8 | public class CreateAccountRequest {
9 |
10 | private final String email, password, name, username;
11 | private final String inviteCode;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/config/Config.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.config;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.TYPE)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface Config {
11 | String directory();
12 |
13 | String name();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/get/model/GetFeedRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.get.model;
2 |
3 | import com.joinhocus.horus.post.PostType;
4 | import lombok.Data;
5 |
6 | import java.util.Set;
7 |
8 | @Data
9 | public class GetFeedRequest {
10 |
11 | private final int page;
12 | private final Set types;
13 | private final AuthorFilter authorFilter;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/Pair.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | @AllArgsConstructor
7 | @Getter
8 | public class Pair {
9 |
10 | private final Key key;
11 | private final Value value;
12 |
13 | public static Pair of(Key key, Value value) {
14 | return new Pair<>(key, value);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/organization/OrganizationSettings.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.organization;
2 |
3 | import lombok.Getter;
4 | import org.bson.Document;
5 |
6 | @Getter
7 | public class OrganizationSettings {
8 |
9 | private final String logo;
10 |
11 | public OrganizationSettings(Document document) {
12 | this.logo = document.getString("logo");
13 | }
14 |
15 | public OrganizationSettings() {
16 | this.logo = null;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/model/CreatePostRequest.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.model;
2 |
3 | import com.joinhocus.horus.post.PostPrivacy;
4 | import com.joinhocus.horus.post.PostType;
5 | import lombok.Data;
6 |
7 | @Data
8 | public class CreatePostRequest {
9 |
10 | private final String content;
11 | private final PostType type;
12 | private final PostPrivacy privacy;
13 | private final String organizationId;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/AccountStatus.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | @AllArgsConstructor
7 | @Getter
8 | public enum AccountStatus {
9 | AVAILABLE(""),
10 | USERNAME_TAKEN("an account with that username already exists"),
11 | EMAIL_TAKEN("an account with that email already exists"),
12 | UNKNOWN("unknown reason, please report this.");
13 |
14 | private final String message;
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/MongoIds.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import lombok.experimental.UtilityClass;
4 | import org.bson.types.ObjectId;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | @UtilityClass
8 | public class MongoIds {
9 |
10 | @Nullable
11 | public ObjectId parseId(String id) {
12 | try {
13 | return new ObjectId(id);
14 | } catch (Exception e) {
15 | return null;
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/slack/horris/BasicHorrisExtension.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.slack.horris;
2 |
3 | import com.joinhocus.horus.slack.SlackClientExtension;
4 | import com.slack.api.bolt.App;
5 |
6 | public class BasicHorrisExtension implements SlackClientExtension {
7 | @Override
8 | public void register(App app) {
9 | app.command("/horris", (request, context) -> {
10 | return context.ack("Hocus pocus, it's time to focus! :sparkles:");
11 | });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/twitter/data/TwitterUser.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.twitter.data;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class TwitterUser {
8 |
9 | private final String name, location, url;
10 | @SerializedName("screen_name")
11 | private final String handle;
12 | @SerializedName("id_str")
13 | private final String id;
14 | private final boolean verified;
15 | @SerializedName("followers_count")
16 | private final int followers;
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/post/Post.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.post;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.organization.Organization;
5 | import org.bson.types.ObjectId;
6 |
7 | import java.util.Date;
8 | import java.util.List;
9 |
10 | public interface Post {
11 |
12 | ObjectId getId();
13 |
14 | PostType type();
15 |
16 | PostPrivacy privacy();
17 |
18 | String content();
19 |
20 | UserAccount author();
21 |
22 | Organization organization();
23 |
24 | Date postTime();
25 |
26 | ObjectId parent();
27 |
28 | void addComment(Post comment);
29 |
30 | List getComments();
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/IOUtils.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 |
9 | @UtilityClass
10 | public class IOUtils {
11 |
12 | public byte[] readBytes(InputStream stream) throws IOException {
13 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
14 | int nRead;
15 | byte[] data = new byte[16384];
16 |
17 | while ((nRead = stream.read(data, 0, data.length)) != -1) {
18 | buffer.write(data, 0, nRead);
19 | }
20 |
21 | return buffer.toByteArray();
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/email/EmailClient.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.email;
2 |
3 | import com.wildbit.java.postmark.Postmark;
4 | import com.wildbit.java.postmark.client.ApiClient;
5 |
6 | public class EmailClient {
7 |
8 | private final String API_TOKEN = "";
9 | private static EmailClient instance;
10 | private final ApiClient apiClient;
11 |
12 | public EmailClient() {
13 | this.apiClient = Postmark.getApiClient(API_TOKEN);
14 | }
15 |
16 | public ApiClient getClient() {
17 | return apiClient;
18 | }
19 |
20 | public static EmailClient getInstance() {
21 | if (instance == null) {
22 | instance = new EmailClient();
23 | }
24 |
25 | return instance;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/gson/ObjectIdSerializer.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc.gson;
2 |
3 | import com.google.gson.JsonDeserializationContext;
4 | import com.google.gson.JsonDeserializer;
5 | import com.google.gson.JsonElement;
6 | import com.google.gson.JsonParseException;
7 | import com.google.gson.JsonPrimitive;
8 | import com.google.gson.JsonSerializationContext;
9 | import com.google.gson.JsonSerializer;
10 | import org.bson.types.ObjectId;
11 |
12 | import java.lang.reflect.Type;
13 |
14 | public class ObjectIdSerializer implements JsonSerializer, JsonDeserializer {
15 |
16 | @Override
17 | public ObjectId deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
18 | return new ObjectId(jsonElement.getAsString());
19 | }
20 |
21 | @Override
22 | public JsonElement serialize(ObjectId id, Type type, JsonSerializationContext jsonSerializationContext) {
23 | return new JsonPrimitive(id.toHexString());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/db/repos/EmailInviteVerificationRepo.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.db.repos;
2 |
3 | import com.google.common.base.Preconditions;
4 | import com.joinhocus.horus.account.invite.Invite;
5 | import com.joinhocus.horus.db.AsyncMongoRepo;
6 | import com.mongodb.client.model.Filters;
7 | import org.bson.Document;
8 |
9 | import java.util.concurrent.CompletableFuture;
10 | import java.util.function.Function;
11 |
12 | public class EmailInviteVerificationRepo extends AsyncMongoRepo {
13 | public EmailInviteVerificationRepo() {
14 | super("hocus", "email_invite_verification");
15 | }
16 |
17 | public CompletableFuture insertVerification(Invite invite, String code) {
18 | Preconditions.checkNotNull(invite.getEmail());
19 |
20 | return this.insertOne(new Document()
21 | .append("inviteId", invite.getInviteId())
22 | .append("code", code)
23 | ).thenApply(opt -> null);
24 | }
25 |
26 | public CompletableFuture getByCode(String code) {
27 | return this.findFirst(Filters.eq("code", code), Function.identity());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/twitter/info/TwitterInfoHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.twitter.info;
2 |
3 | import com.joinhocus.horus.http.DefinedTypesHandler;
4 | import com.joinhocus.horus.http.Response;
5 | import com.joinhocus.horus.http.model.EmptyRequest;
6 | import com.joinhocus.horus.twitter.TwitterAPI;
7 | import io.javalin.core.validation.BodyValidator;
8 | import io.javalin.http.Context;
9 | import org.slf4j.Logger;
10 |
11 | import java.util.concurrent.CompletableFuture;
12 |
13 | public class TwitterInfoHandler implements DefinedTypesHandler {
14 | @Override
15 | public CompletableFuture handle(BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
16 | String twitterId = context.pathParam("twitterId");
17 | return TwitterAPI.fetchUserInfo(twitterId).thenApply(user -> {
18 | return Response.of(Response.Type.OKAY)
19 | .append("handle", user.getHandle())
20 | .append("name", user.getName());
21 | });
22 | }
23 |
24 | @Override
25 | public Class extends EmptyRequest> requestClass() {
26 | return EmptyRequest.class;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/get/CheckAccountUsernameAvailabilityHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.get;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.http.DefinedTypesHandler;
5 | import com.joinhocus.horus.http.Response;
6 | import com.joinhocus.horus.http.model.EmptyRequest;
7 | import com.joinhocus.horus.misc.HandlesUtil;
8 | import io.javalin.core.validation.BodyValidator;
9 | import io.javalin.http.Context;
10 | import org.slf4j.Logger;
11 |
12 | import java.util.concurrent.CompletableFuture;
13 |
14 | public class CheckAccountUsernameAvailabilityHandler implements DefinedTypesHandler {
15 | @Override
16 | public CompletableFuture handle(BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
17 | String name = context.queryParam("username");
18 | if (Strings.isNullOrEmpty(name)) {
19 | return wrap(Response.of(Response.Type.BAD_REQUEST));
20 | }
21 | return HandlesUtil.isHandleAvailable(name).thenApply(available -> {
22 | return Response.of(Response.Type.OKAY).append("available", available);
23 | });
24 | }
25 |
26 | @Override
27 | public Class extends EmptyRequest> requestClass() {
28 | return EmptyRequest.class;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/config/Configs.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.config;
2 |
3 | import com.google.common.base.Preconditions;
4 | import com.google.gson.Gson;
5 | import com.google.gson.GsonBuilder;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileNotFoundException;
10 | import java.io.InputStreamReader;
11 | import java.util.function.Function;
12 |
13 | public class Configs {
14 | private static final Gson GSON = new GsonBuilder().serializeNulls().create();
15 |
16 | public static T load(Class clazz, Function, T> fallback) {
17 | Config config = clazz.getAnnotation(Config.class);
18 | Preconditions.checkNotNull(config, "Config not present");
19 |
20 | T object = null;
21 | File file = new File(
22 | "configs/" + config.directory(),
23 | config.name() + ".json"
24 | );
25 | try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file))) {
26 | object = GSON.fromJson(reader, clazz);
27 | } catch (Exception e) {
28 | if (e instanceof FileNotFoundException) {
29 | if (fallback != null) {
30 | object = fallback.apply(clazz);
31 | }
32 | }
33 |
34 | e.printStackTrace();
35 | }
36 |
37 | return object;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/Environment.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import com.google.common.base.Strings;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | @AllArgsConstructor
10 | @Getter
11 | public enum Environment {
12 | PRODUCTION("https://joinhocus.com"),
13 | REVIEW("https://review.feed.horris.dev"), // TODO change this when working on branches w/ frontend
14 | STAGING("https://staging.horris.dev"),
15 | DEVELOPMENT("http://localhost:3000");
16 |
17 | private final String url;
18 | private static final Environment current;
19 |
20 | static {
21 | Logger logger = LoggerFactory.getLogger(Environment.class);
22 | String env = System.getenv("ENVIRONMENT");
23 | if (Strings.isNullOrEmpty(env)) {
24 | if (!Strings.isNullOrEmpty(System.getenv("GIT_HASH"))) {
25 | current = PRODUCTION;
26 | } else {
27 | current = DEVELOPMENT;
28 | }
29 | } else {
30 | current = Environment.valueOf(env);
31 | }
32 |
33 | logger.info("Loaded environment {}", current.name());
34 | }
35 |
36 | public static boolean isDev() {
37 | return current == DEVELOPMENT || current == STAGING;
38 | }
39 |
40 | public static Environment current() {
41 | return current;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/join/JoinWaitlistTwitterHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.join;
2 |
3 | import com.joinhocus.horus.http.DefinedTypesHandler;
4 | import com.joinhocus.horus.http.Response;
5 | import com.joinhocus.horus.http.model.EmptyRequest;
6 | import com.joinhocus.horus.misc.Environment;
7 | import com.joinhocus.horus.twitter.TwitterAPI;
8 | import io.javalin.core.validation.BodyValidator;
9 | import io.javalin.http.Context;
10 | import io.javalin.http.InternalServerErrorResponse;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public class JoinWaitlistTwitterHandler implements DefinedTypesHandler {
16 |
17 | private final String CALLBACK_URL = Environment.current().getUrl() + "/join";
18 |
19 | @Override
20 | public CompletableFuture handle(BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
21 | return TwitterAPI.OAUTH.getRequestToken(CALLBACK_URL).thenApply(res -> {
22 | return Response.of(Response.Type.OKAY).append("token", res.getToken());
23 | }).exceptionally(err -> {
24 | logger.error("", err);
25 | throw new InternalServerErrorResponse();
26 | });
27 | }
28 |
29 | @Override
30 | public Class extends EmptyRequest> requestClass() {
31 | return EmptyRequest.class;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/register/twitter/RegisterWithTwitterHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.register.twitter;
2 |
3 | import com.joinhocus.horus.http.DefinedTypesHandler;
4 | import com.joinhocus.horus.http.Response;
5 | import com.joinhocus.horus.http.model.EmptyRequest;
6 | import com.joinhocus.horus.misc.Environment;
7 | import com.joinhocus.horus.twitter.TwitterAPI;
8 | import io.javalin.core.validation.BodyValidator;
9 | import io.javalin.http.Context;
10 | import io.javalin.http.InternalServerErrorResponse;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public class RegisterWithTwitterHandler implements DefinedTypesHandler {
16 |
17 | private final String CALLBACK_URL = Environment.current().getUrl() + "/flow/twitter";
18 | @Override
19 | public CompletableFuture handle(BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
20 | return TwitterAPI.OAUTH.getRequestToken(CALLBACK_URL).thenApply(res -> {
21 | return Response.of(Response.Type.OKAY).append("token", res.getToken());
22 | }).exceptionally(err -> {
23 | logger.error("", err);
24 | throw new InternalServerErrorResponse();
25 | });
26 | }
27 |
28 | @Override
29 | public Class extends EmptyRequest> requestClass() {
30 | return EmptyRequest.class;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/activity/ActivityNotification.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account.activity;
2 |
3 | import com.joinhocus.horus.misc.follow.EntityType;
4 | import lombok.Getter;
5 | import lombok.RequiredArgsConstructor;
6 | import lombok.ToString;
7 | import org.bson.Document;
8 | import org.bson.types.ObjectId;
9 |
10 | import java.util.Date;
11 | import java.util.List;
12 |
13 | @RequiredArgsConstructor
14 | @Getter
15 | @ToString
16 | public class ActivityNotification {
17 |
18 | private final ObjectId id;
19 | private final ObjectId actor;
20 | private final EntityType entityType;
21 | private final List receivers;
22 | private final Date when;
23 | private final NotificationType notificationType;
24 | private final Document extra;
25 |
26 | private boolean wasSeen;
27 |
28 | public ActivityNotification(Document document) {
29 | this.id = document.getObjectId("_id");
30 | this.actor = document.getObjectId("actor");
31 | this.entityType = EntityType.valueOf(document.getString("entity"));
32 | //noinspection unchecked
33 | this.receivers = (List) document.get("receivers");
34 | this.when = document.getDate("when");
35 | this.notificationType = NotificationType.valueOf(document.getString("type"));
36 | this.extra = document.get("extra", Document.class);
37 | this.wasSeen = document.getBoolean("wasSeen", false);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/db/config/MongoConfig.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.db.config;
2 |
3 | import com.joinhocus.horus.config.Config;
4 | import com.mongodb.ServerAddress;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | @Config(directory = "database", name = "mongo")
10 | public class MongoConfig {
11 |
12 | private final String username, password;
13 | private final List addresses;
14 |
15 | public MongoConfig(
16 | String username,
17 | String password,
18 | List addresses)
19 | {
20 | this.username = username;
21 | this.password = password;
22 | this.addresses = addresses;
23 | }
24 |
25 | public String getUsername() {
26 | return username;
27 | }
28 |
29 | public String getPassword() {
30 | return password;
31 | }
32 |
33 | public List getAddresses() {
34 | return addresses;
35 | }
36 |
37 | public List asServerAddresses() {
38 | List addresses = new ArrayList<>(getAddresses().size());
39 | for (String address : getAddresses()) {
40 | addresses.add(new ServerAddress(address));
41 | }
42 |
43 | return addresses;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return "MongoConfig{" +
49 | "username='" + username + '\'' +
50 | ", password='" + password + '\'' +
51 | ", addresses=" + addresses +
52 | '}';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/entity/SearchByHandleHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.entity;
2 |
3 | import com.google.common.base.Strings;
4 | import com.google.gson.JsonArray;
5 | import com.google.gson.JsonObject;
6 | import com.joinhocus.horus.account.UserAccount;
7 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.model.EmptyRequest;
10 | import com.joinhocus.horus.misc.HandlesUtil;
11 | import io.javalin.core.validation.BodyValidator;
12 | import io.javalin.http.Context;
13 | import org.slf4j.Logger;
14 |
15 | import java.util.concurrent.CompletableFuture;
16 |
17 | public class SearchByHandleHandler implements DefinedTypesWithUserHandler {
18 | @Override
19 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
20 | String query = context.queryParam("query");
21 | if (Strings.isNullOrEmpty(query)) {
22 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("query cannot be empty"));
23 | }
24 |
25 | return HandlesUtil.getHandlesByQuery(query).thenApply(list -> {
26 | JsonArray array = new JsonArray();
27 | for (JsonObject object : list) {
28 | array.add(object);
29 | }
30 |
31 | return Response.of(Response.Type.OKAY).append("results", array);
32 | });
33 | }
34 |
35 | @Override
36 | public Class extends EmptyRequest> requestClass() {
37 | return EmptyRequest.class;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/db/repos/LikesRepo.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.db.repos;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.db.AsyncMongoRepo;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.mongodb.client.model.Filters;
7 | import org.bson.Document;
8 | import org.bson.types.ObjectId;
9 |
10 | import java.util.concurrent.CompletableFuture;
11 |
12 | public class LikesRepo extends AsyncMongoRepo {
13 | public LikesRepo() {
14 | super("hocus", "likes");
15 | }
16 |
17 | public CompletableFuture hasLiked(UserAccount liker, ObjectId postId) {
18 | return this.checkExists(Filters.and(
19 | Filters.eq("liker", liker.getId()),
20 | Filters.eq("post", postId)
21 | ));
22 | }
23 |
24 | public CompletableFuture createLike(UserAccount liker, ObjectId postId) {
25 | return this.insertOne(
26 | new Document()
27 | .append("liker", liker.getId())
28 | .append("post", postId)
29 | ).thenApply(ignored -> null).thenCompose(aVoid -> {
30 | return MongoDatabase.getInstance().getRepo(PostsRepo.class).incLikes(postId, 1);
31 | });
32 | }
33 |
34 | public CompletableFuture deleteLike(UserAccount liker, ObjectId postId) {
35 | return this.deleteOne(Filters.and(
36 | Filters.eq("liker", liker.getId()),
37 | Filters.eq("post", postId)
38 | )).thenCompose(ignored -> {
39 | return MongoDatabase.getInstance().getRepo(PostsRepo.class).incLikes(postId, -1);
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/settings/UpdateAccountAvatarHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.settings;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.db.MongoDatabase;
5 | import com.joinhocus.horus.db.repos.AccountsRepo;
6 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
7 | import com.joinhocus.horus.http.Response;
8 | import com.joinhocus.horus.http.model.EmptyRequest;
9 | import io.javalin.core.validation.BodyValidator;
10 | import io.javalin.http.Context;
11 | import io.javalin.http.UploadedFile;
12 | import org.slf4j.Logger;
13 |
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | public class UpdateAccountAvatarHandler implements DefinedTypesWithUserHandler {
17 | @Override
18 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
19 | UploadedFile file = context.uploadedFile("file");
20 | if (file == null) {
21 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("file cannot be empty"));
22 | }
23 |
24 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class).setAvatar(
25 | account.getId(),
26 | file
27 | ).thenApply(success -> {
28 | if (!success) {
29 | return Response.of(Response.Type.BAD_REQUEST).setMessage("failed to update avatar");
30 | }
31 |
32 | return Response.of(Response.Type.OKAY);
33 | });
34 | }
35 |
36 | @Override
37 | public Class extends EmptyRequest> requestClass() {
38 | return EmptyRequest.class;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/get/GetAccountHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.get;
2 |
3 | import com.google.gson.JsonObject;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
6 | import com.joinhocus.horus.http.Response;
7 | import com.joinhocus.horus.http.model.EmptyRequest;
8 | import io.javalin.core.validation.BodyValidator;
9 | import io.javalin.http.Context;
10 | import org.bson.Document;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public class GetAccountHandler implements DefinedTypesWithUserHandler {
16 | @Override
17 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
18 | JsonObject object = new JsonObject();
19 |
20 | object.addProperty("id", account.getId().toHexString());
21 | object.addProperty("handle", account.getUsernames().getDisplay());
22 | object.addProperty("name", account.getName());
23 | object.addProperty("finishedSetup", account.isFinishedAccountSetup());
24 | object.addProperty("email", account.getEmail());
25 |
26 | Document extra = account.getExtra();
27 | JsonObject extraInfo = new JsonObject();
28 | if (extra != null) {
29 | int invites = extra.getInteger("invites", 0);
30 | extraInfo.addProperty("invites", invites);
31 | }
32 |
33 | object.add("extra", extraInfo);
34 | return wrap(Response.of(Response.Type.OKAY).append("account", object));
35 | }
36 |
37 | @Override
38 | public Class extends EmptyRequest> requestClass() {
39 | return EmptyRequest.class;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/entity/GetFollowsEntityHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.entity;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.FollowsRepo;
7 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.model.EmptyRequest;
10 | import com.joinhocus.horus.misc.MongoIds;
11 | import io.javalin.core.validation.BodyValidator;
12 | import io.javalin.http.Context;
13 | import org.bson.types.ObjectId;
14 | import org.slf4j.Logger;
15 |
16 | import java.util.concurrent.CompletableFuture;
17 |
18 | public class GetFollowsEntityHandler implements DefinedTypesWithUserHandler {
19 | @Override
20 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
21 | String id = context.queryParam("id");
22 | if (Strings.isNullOrEmpty(id)) {
23 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("id cannot be empty"));
24 | }
25 | ObjectId mongoId = MongoIds.parseId(id);
26 | if (mongoId == null) {
27 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("malformed id"));
28 | }
29 |
30 | return MongoDatabase.getInstance().getRepo(FollowsRepo.class).follows(account, mongoId).thenApply(follows -> {
31 | return Response.of(Response.Type.OKAY).append("follows", follows);
32 | });
33 | }
34 |
35 | @Override
36 | public Class extends EmptyRequest> requestClass() {
37 | return EmptyRequest.class;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/invite/Invite.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account.invite;
2 |
3 | import com.google.common.base.Strings;
4 | import lombok.Getter;
5 | import org.bson.Document;
6 | import org.bson.types.ObjectId;
7 |
8 | @Getter
9 | public class Invite {
10 |
11 | // either of these two MUST be present
12 | private String inviteId;
13 |
14 | private final String twitterId, email;
15 | private final ObjectId inviter; // if this is null = horris invited.
16 | private final String orgId;
17 | private final String code;
18 |
19 | private boolean wasClaimed;
20 |
21 | public Invite(Document document) {
22 | this.inviteId = document.getObjectId("_id").toHexString();
23 | this.twitterId = document.getString("twitterId");
24 | this.email = document.getString("email");
25 | this.inviter = document.getObjectId("inviter");
26 | this.orgId = document.getString("orgId");
27 | this.code = document.getString("code");
28 | this.wasClaimed = document.getBoolean("claimed", false);
29 | }
30 |
31 | public Invite(String twitterId, String email, ObjectId inviter, String orgId, String code) {
32 | if (Strings.isNullOrEmpty(twitterId) && Strings.isNullOrEmpty(email)) {
33 | throw new IllegalStateException("twitterId or email must be provided");
34 | }
35 | this.twitterId = twitterId;
36 | this.email = email;
37 | this.inviter = inviter;
38 | this.orgId = orgId;
39 | this.code = code;
40 | }
41 |
42 | public Document toDocument() {
43 | return new Document()
44 | .append("twitterId", this.twitterId)
45 | .append("email", this.email)
46 | .append("inviter", this.inviter)
47 | .append("orgId", this.orgId)
48 | .append("code", this.code)
49 | .append("generatedAt", System.currentTimeMillis());
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/db/repos/PasswordResetRepo.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.db.repos;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.db.AsyncMongoRepo;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.mongodb.client.model.Filters;
7 | import com.mongodb.client.model.Updates;
8 | import org.bson.Document;
9 | import org.bson.types.ObjectId;
10 |
11 | import java.util.UUID;
12 | import java.util.concurrent.CompletableFuture;
13 | import java.util.function.Function;
14 |
15 | public class PasswordResetRepo extends AsyncMongoRepo {
16 | public PasswordResetRepo() {
17 | super("hocus", "password_resets");
18 | }
19 |
20 | public CompletableFuture createResetCode(UserAccount account, UUID code, String ip) {
21 | return this.insertOne(new Document()
22 | .append("for", account.getId())
23 | .append("code", code.toString())
24 | .append("createdAt", System.currentTimeMillis())
25 | .append("requestingIp", ip)
26 | );
27 | }
28 |
29 | public CompletableFuture consumeCode(String code) {
30 | return this.updateOne(Filters.eq("code", code), Updates.set("consumed", true)).thenApply(success -> null);
31 | }
32 |
33 | public CompletableFuture getUserAccountFor(String token) {
34 | return this.findFirst(Filters.and(
35 | Filters.eq("code", token),
36 | Filters.exists("consumed", false)
37 | ), Function.identity())
38 | .thenCompose(opt -> {
39 | if (opt == null) {
40 | return CompletableFuture.completedFuture(null);
41 | }
42 |
43 | ObjectId forUser = opt.getObjectId("for");
44 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class)
45 | .findById(forUser);
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/code/ValidateInviteCodeHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.code;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.db.MongoDatabase;
5 | import com.joinhocus.horus.db.repos.InvitesRepo;
6 | import com.joinhocus.horus.http.DefinedTypesHandler;
7 | import com.joinhocus.horus.http.Response;
8 | import com.joinhocus.horus.http.routes.account.code.model.InviteCodeRequest;
9 | import io.javalin.core.validation.BodyValidator;
10 | import io.javalin.http.Context;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public class ValidateInviteCodeHandler implements DefinedTypesHandler {
16 | @Override
17 | public CompletableFuture handle(BodyValidator extends InviteCodeRequest> validator, Context context, Logger logger) throws Exception {
18 | InviteCodeRequest request = validator
19 | .check("code", req -> !Strings.isNullOrEmpty(req.getCode()), "code cannot be empty")
20 | .get();
21 | return runPipeline(request.getCode());
22 | }
23 |
24 | private CompletableFuture runPipeline(String code) {
25 | return MongoDatabase.getInstance().getRepo(InvitesRepo.class)
26 | .getInviteByCode(code)
27 | .thenCompose(invite -> {
28 | if (invite == null) {
29 | return wrap(Response.of(Response.Type.OKAY).append("valid", false));
30 | }
31 | if (invite.isWasClaimed()) {
32 | return wrap(Response.of(Response.Type.OKAY).append("valid", false));
33 | }
34 |
35 | return wrap(Response.of(Response.Type.OKAY).append("valid", true));
36 | });
37 | }
38 |
39 | @Override
40 | public Class extends InviteCodeRequest> requestClass() {
41 | return InviteCodeRequest.class;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/slack/SlackEventsHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.slack;
2 |
3 | import com.google.common.base.Joiner;
4 | import com.slack.api.bolt.request.Request;
5 | import io.javalin.http.Context;
6 | import io.javalin.http.Handler;
7 | import io.javalin.http.InternalServerErrorResponse;
8 | import org.jetbrains.annotations.NotNull;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | public class SlackEventsHandler implements Handler {
16 |
17 | @Override
18 | public void handle(@NotNull Context context) {
19 | Logger logger = LoggerFactory.getLogger(getClass());
20 | Request> slackReq = SlackClient.getInstance().transform(context);
21 | if (slackReq != null) {
22 | com.slack.api.bolt.context.Context slackContext = slackReq.getContext();
23 | slackContext.setBotToken(SlackClient.XOXB_TOKEN);
24 | try {
25 | com.slack.api.bolt.response.Response slackResponse = SlackClient.getInstance().getApp().run(slackReq);
26 | if (slackResponse != null) {
27 | context.status(slackResponse.getStatusCode());
28 | for (Map.Entry> header : slackResponse.getHeaders().entrySet()) {
29 | String name = header.getKey();
30 | context.header(name, Joiner.on(",").join(header.getValue()));
31 | }
32 | if (slackResponse.getBody() != null) {
33 | context.result(slackResponse.getBody());
34 | } else {
35 | context.status(404); // not found
36 | // if it's null we let it time out, slack handles that for us
37 | }
38 | }
39 | } catch (Exception e) {
40 | logger.error("", e);
41 | context.json(new InternalServerErrorResponse());
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/DefinedTypesWithUserHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http;
2 |
3 | import com.joinhocus.horus.account.AccountAuth;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.AccountsRepo;
7 | import com.joinhocus.horus.misc.CompletableFutures;
8 | import io.javalin.core.validation.BodyValidator;
9 | import io.javalin.http.BadRequestResponse;
10 | import io.javalin.http.Context;
11 | import org.slf4j.Logger;
12 |
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public interface DefinedTypesWithUserHandler extends DefinedTypesHandler {
16 |
17 | @Override
18 | default CompletableFuture handle(BodyValidator extends Request> validator, Context context, Logger logger) throws Exception {
19 | String token = context.header("Authorization");
20 | if (token == null) {
21 | throw new BadRequestResponse("Not logged in");
22 | }
23 | if (!token.startsWith("Bearer ")) {
24 | throw new BadRequestResponse("Malformed Authorization header");
25 | }
26 | token = token.replaceFirst("Bearer ", "");
27 | String account = AccountAuth.getAccountFromJwt(token);
28 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class)
29 | .findByLogin(account)
30 | .thenCompose(userAccount -> {
31 | if (userAccount == null) {
32 | return wrap(Response.of(Response.Type.UNAUTHORIZED));
33 | }
34 |
35 | try {
36 | return handle(userAccount, validator, context, logger);
37 | } catch (Exception e) {
38 | return CompletableFutures.failedFuture(e);
39 | }
40 | });
41 | }
42 |
43 | CompletableFuture handle(UserAccount account, BodyValidator extends Request> validator, Context context, Logger logger) throws Exception;
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/activity/GetActivityCountHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.activity;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.ActivityRepo;
7 | import com.joinhocus.horus.db.repos.OrganizationRepo;
8 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
9 | import com.joinhocus.horus.http.Response;
10 | import com.joinhocus.horus.http.model.EmptyRequest;
11 | import com.joinhocus.horus.organization.Organization;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.bson.types.ObjectId;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.concurrent.CompletableFuture;
18 | import java.util.stream.Collectors;
19 |
20 | public class GetActivityCountHandler implements DefinedTypesWithUserHandler {
21 | @Override
22 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
23 | return MongoDatabase.getInstance().getRepo(OrganizationRepo.class).getOrganizations(account.getId()).thenApply(orgs -> {
24 | return ImmutableList.builder()
25 | .add(account.getId())
26 | .addAll(orgs.stream().map(Organization::getId).collect(Collectors.toList()))
27 | .build();
28 | }).thenCompose(ids -> {
29 | return MongoDatabase.getInstance().getRepo(ActivityRepo.class).countActivity(
30 | account.getId(),
31 | ids
32 | );
33 | }).thenApply(notifications -> {
34 | return Response.of(Response.Type.OKAY).append("amount", notifications);
35 | });
36 | }
37 |
38 | @Override
39 | public Class extends EmptyRequest> requestClass() {
40 | return EmptyRequest.class;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/changepw/ChangePasswordHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.changepw;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.AccountsRepo;
7 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.routes.account.changepw.model.ChangePasswordRequest;
10 | import io.javalin.core.validation.BodyValidator;
11 | import io.javalin.http.Context;
12 | import org.slf4j.Logger;
13 |
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | public class ChangePasswordHandler implements DefinedTypesWithUserHandler {
17 | @Override
18 | public CompletableFuture handle(UserAccount account, BodyValidator extends ChangePasswordRequest> validator, Context context, Logger logger) throws Exception {
19 | ChangePasswordRequest request = validator
20 | .check("current", req -> !Strings.isNullOrEmpty(req.getCurrent()), "current cannot be empty")
21 | .check("changeTo", req -> !Strings.isNullOrEmpty(req.getChangeTo()), "changeTo cannot be empty")
22 | .get();
23 |
24 | boolean isValid = account.passwordsMatch(request.getCurrent());
25 | if (!isValid) {
26 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("Incorrect current password provided."));
27 | }
28 |
29 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class).changePassword(account.getId(), request.getChangeTo()).thenApply(success -> {
30 | if (success) {
31 | return Response.of(Response.Type.OKAY);
32 | }
33 |
34 | return Response.of(Response.Type.BAD_REQUEST).setMessage("Failed to update password");
35 | });
36 | }
37 |
38 | @Override
39 | public Class extends ChangePasswordRequest> requestClass() {
40 | return ChangePasswordRequest.class;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/setup/SetupAccountHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.setup;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.AccountType;
5 | import com.joinhocus.horus.account.UserAccount;
6 | import com.joinhocus.horus.db.MongoDatabase;
7 | import com.joinhocus.horus.db.repos.AccountsRepo;
8 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
9 | import com.joinhocus.horus.http.Response;
10 | import com.joinhocus.horus.http.routes.account.setup.model.SetupAccountRequest;
11 | import com.joinhocus.horus.misc.CompletableFutures;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.slf4j.Logger;
15 |
16 | import java.util.concurrent.CompletableFuture;
17 |
18 | public class SetupAccountHandler implements DefinedTypesWithUserHandler {
19 | @Override
20 | public CompletableFuture handle(UserAccount account, BodyValidator extends SetupAccountRequest> validator, Context context, Logger logger) throws Exception {
21 | SetupAccountRequest request = validator
22 | .check("selected", req -> !Strings.isNullOrEmpty(req.getSelected()), "selected cannot be empty")
23 | .get();
24 | if (account.isFinishedAccountSetup()) {
25 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("This account has already been setup"));
26 | }
27 | AccountType type = AccountType.valueOf(request.getSelected().toUpperCase());
28 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class).setAccountType(
29 | account.getId(),
30 | type
31 | ).thenCompose(success -> {
32 | if (!success) {
33 | return CompletableFutures.failedFuture(new IllegalStateException("database failed to acknowledge update"));
34 | }
35 |
36 | return wrap(Response.of(Response.Type.OKAY));
37 | });
38 | }
39 |
40 | @Override
41 | public Class extends SetupAccountRequest> requestClass() {
42 | return SetupAccountRequest.class;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/account/AccountAuth.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.account;
2 |
3 | import com.auth0.jwt.JWT;
4 | import com.auth0.jwt.JWTVerifier;
5 | import com.auth0.jwt.algorithms.Algorithm;
6 | import com.auth0.jwt.interfaces.Claim;
7 | import com.auth0.jwt.interfaces.DecodedJWT;
8 | import de.mkammerer.argon2.Argon2;
9 | import de.mkammerer.argon2.Argon2Factory;
10 |
11 | import java.util.Date;
12 | import java.util.regex.Pattern;
13 |
14 | public final class AccountAuth {
15 |
16 | private static final String SECRET = "";
17 | private static final Algorithm HMAC = Algorithm.HMAC256(SECRET);
18 | private static final String ISSUER = "horus";
19 |
20 | public static final Argon2 ARGON_2 = Argon2Factory.create(
21 | Argon2Factory.Argon2Types.ARGON2id,
22 | 32,
23 | 64
24 | );
25 |
26 | private static final Pattern EMAIL_PATTERN = Pattern.compile("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])");
27 |
28 | public static String getToken(UserAccount account) {
29 | return JWT.create()
30 | .withIssuer(ISSUER)
31 | .withClaim("account", account.getUsernames().getName())
32 | .withIssuedAt(new Date())
33 | .sign(HMAC);
34 | }
35 |
36 | public static String getAccountFromJwt(String token) {
37 | JWTVerifier verifier = JWT.require(HMAC)
38 | .withIssuer(ISSUER)
39 | .build();
40 |
41 | DecodedJWT jwt = verifier.verify(token);
42 | Claim account = jwt.getClaim("account");
43 | return account.asString();
44 | }
45 |
46 | public static boolean isValidEmail(String email) {
47 | return EMAIL_PATTERN.matcher(email).matches();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/create/ValidateInviteTwitterComboHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.create;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.db.MongoDatabase;
5 | import com.joinhocus.horus.db.repos.WaitListRepo;
6 | import com.joinhocus.horus.http.DefinedTypesHandler;
7 | import com.joinhocus.horus.http.Response;
8 | import com.joinhocus.horus.http.routes.account.create.model.VerifyComboRequest;
9 | import com.joinhocus.horus.twitter.TwitterAPI;
10 | import io.javalin.core.validation.BodyValidator;
11 | import io.javalin.http.Context;
12 | import org.slf4j.Logger;
13 |
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | public class ValidateInviteTwitterComboHandler implements DefinedTypesHandler {
17 | @Override
18 | public CompletableFuture handle(BodyValidator extends VerifyComboRequest> validator, Context context, Logger logger) throws Exception {
19 | VerifyComboRequest request = validator
20 | .check("inviteCode", req -> !Strings.isNullOrEmpty(req.getInviteCode()), "inviteCode cannot be empty")
21 | .check("twitterId", req -> !Strings.isNullOrEmpty(req.getTwitterId()), "twitterId cannot be empty")
22 | .get();
23 |
24 | return MongoDatabase.getInstance().getRepo(WaitListRepo.class)
25 | .checkCombination(request.getInviteCode(), request.getTwitterId())
26 | .thenCompose(valid -> {
27 | if (!valid) {
28 | return wrap(Response.of(Response.Type.OKAY).append("valid", false));
29 | }
30 |
31 | return doComplexPipeline(request.getTwitterId());
32 | });
33 | }
34 |
35 | private CompletableFuture doComplexPipeline(String twitterId) {
36 | return TwitterAPI.fetchUserInfo(twitterId).thenApply(user -> {
37 | return Response.of(Response.Type.OKAY).append("valid", true).append("handle", user.getHandle());
38 | });
39 | }
40 |
41 | @Override
42 | public Class extends VerifyComboRequest> requestClass() {
43 | return VerifyComboRequest.class;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/db/repos/InvitesRepo.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.db.repos;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.joinhocus.horus.account.invite.Invite;
5 | import com.joinhocus.horus.db.AsyncMongoRepo;
6 | import com.mongodb.client.model.Filters;
7 | import com.mongodb.client.model.Updates;
8 | import org.bson.Document;
9 | import org.bson.types.ObjectId;
10 |
11 | import java.util.concurrent.CompletableFuture;
12 | import java.util.function.Function;
13 |
14 | public class InvitesRepo extends AsyncMongoRepo {
15 | public InvitesRepo() {
16 | super("hocus", "invites");
17 | }
18 |
19 | public CompletableFuture insertInvite(Invite invite) {
20 | return this.insertOne(invite.toDocument());
21 | }
22 |
23 | public CompletableFuture getInviteById(ObjectId id) {
24 | return this.findFirst(
25 | Filters.eq("_id", id),
26 | Function.identity()
27 | );
28 | }
29 |
30 | public CompletableFuture getInviteByCode(String code) {
31 | return this.findFirst(
32 | Filters.eq("code", code),
33 | Invite::new
34 | );
35 | }
36 |
37 | public CompletableFuture checkExists(String code) {
38 | return this.checkExists(
39 | Filters.and(
40 | Filters.eq("code", code),
41 | Filters.exists("claimed", false)
42 | )
43 | );
44 | }
45 |
46 | public CompletableFuture checkTwitterExists(String code) {
47 | return this.checkExists(Filters.eq("twitterId", code));
48 | }
49 |
50 | public CompletableFuture checkEmailExists(String code) {
51 | return this.checkExists(Filters.eq("email", code));
52 | }
53 |
54 | public CompletableFuture claim(String code) {
55 | return this.updateOne(
56 | Filters.eq("code", code),
57 | Updates.set("claimed", true)
58 | ).thenApply(aBoolean -> null);
59 | }
60 |
61 | public CompletableFuture> getInvitesFrom(ObjectId inviter) {
62 | return this.find(Filters.eq("inviter", inviter), null, Invite::new);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/organization/getbyuser/GetUserOrganizationsHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.organization.getbyuser;
2 |
3 | import com.google.gson.JsonArray;
4 | import com.google.gson.JsonObject;
5 | import com.joinhocus.horus.account.UserAccount;
6 | import com.joinhocus.horus.db.MongoDatabase;
7 | import com.joinhocus.horus.db.repos.OrganizationRepo;
8 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
9 | import com.joinhocus.horus.http.Response;
10 | import com.joinhocus.horus.http.model.EmptyRequest;
11 | import com.joinhocus.horus.organization.Organization;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.slf4j.Logger;
15 |
16 | import java.util.concurrent.CompletableFuture;
17 |
18 | public class GetUserOrganizationsHandler implements DefinedTypesWithUserHandler {
19 | @Override
20 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
21 | return MongoDatabase.getInstance().getRepo(OrganizationRepo.class)
22 | .getOrganizations(account.getId())
23 | .thenApply(organizations -> {
24 | JsonArray orgs = new JsonArray();
25 | for (Organization organization : organizations) {
26 | JsonObject object = new JsonObject();
27 | object.addProperty("id", organization.getId().toHexString());
28 | object.addProperty("name", organization.getName());
29 | object.addProperty("handle", organization.getHandle());
30 |
31 | JsonObject settings = new JsonObject();
32 | settings.addProperty("logo", organization.getSettings().getLogo());
33 | object.add("settings", settings);
34 |
35 | orgs.add(object);
36 | }
37 |
38 | return Response.of(Response.Type.OKAY).append("organizations", orgs);
39 | });
40 | }
41 |
42 | @Override
43 | public Class extends EmptyRequest> requestClass() {
44 | return EmptyRequest.class;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/slack/waitlist/SlackWaitListExtension.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.slack.waitlist;
2 |
3 | import com.joinhocus.horus.slack.SlackClientExtension;
4 | import com.slack.api.bolt.App;
5 | import com.slack.api.bolt.response.Response;
6 | import com.slack.api.methods.response.views.ViewsOpenResponse;
7 | import com.slack.api.model.block.Blocks;
8 | import com.slack.api.model.block.composition.BlockCompositions;
9 | import com.slack.api.model.block.element.BlockElements;
10 | import com.slack.api.model.view.Views;
11 |
12 | public class SlackWaitListExtension implements SlackClientExtension {
13 | @Override
14 | public void register(App app) {
15 | app.blockAction("give_access", (request, context) -> {
16 | context.respond("Not implemented yet!");
17 | return context.ack();
18 | });
19 |
20 | app.command("/waitlist", (request, context) -> {
21 | ViewsOpenResponse viewsOpen = context.client().viewsOpen(r -> r.triggerId(context.getTriggerId()).view(
22 | Views.view(view -> view
23 | .callbackId("invite_direct")
24 | .type("modal")
25 | .notifyOnClose(true)
26 | .title(Views.viewTitle(title -> title.type("plain_text").text("Invite a Twitter user to Hocus")))
27 | .submit(Views.viewSubmit(submit -> submit.type("plain_text").text("Invite!")))
28 | .close(Views.viewClose(close -> close.type("plain_text").text("Cancel")))
29 | .blocks(Blocks.asBlocks(
30 | Blocks.input(input -> {
31 | input.blockId("handle-block")
32 | .element(BlockElements.plainTextInput(pti -> pti.actionId("handle-action")))
33 | .label(BlockCompositions.plainText("Twitter Handle"));
34 | return input;
35 | })
36 | )))
37 | ));
38 | if (viewsOpen.isOk()) return context.ack();
39 | return Response.builder().statusCode(500).body(viewsOpen.getError()).build();
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/post/PostUtil.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.post;
2 |
3 | import com.google.gson.JsonArray;
4 | import com.google.gson.JsonObject;
5 | import com.joinhocus.horus.account.UserAccount;
6 | import com.joinhocus.horus.organization.Organization;
7 | import com.joinhocus.horus.post.impl.PostWithExtra;
8 | import lombok.experimental.UtilityClass;
9 |
10 | @UtilityClass
11 | public class PostUtil {
12 |
13 | public JsonObject recursive(Post post) {
14 | JsonObject parent = new JsonObject();
15 | JsonArray replies = new JsonArray();
16 |
17 | parent.addProperty("id", post.getId().toHexString());
18 | UserAccount authorAccount = post.author();
19 | JsonObject author = new JsonObject();
20 | if (authorAccount != null) {
21 | author.addProperty("id", authorAccount.getId().toHexString());
22 | author.addProperty("name", authorAccount.getName());
23 | author.addProperty("handle", authorAccount.getUsernames().getDisplay());
24 | }
25 |
26 | JsonObject organization = new JsonObject();
27 | Organization organizationObject = post.organization();
28 | if (organizationObject != null) {
29 | organization.addProperty("id", organizationObject.getId().toHexString());
30 | organization.addProperty("name", organizationObject.getName());
31 | organization.addProperty("logo", organizationObject.getSettings().getLogo());
32 | }
33 |
34 | parent.addProperty("content", post.content());
35 | if (post.type() != null) {
36 | parent.addProperty("type", post.type().name());
37 | }
38 | parent.addProperty("postTime", post.postTime().getTime());
39 |
40 | if (post instanceof PostWithExtra) {
41 | PostWithExtra extra = (PostWithExtra) post;
42 | JsonObject extraData = extra.getExtra();
43 | parent.add("extra", extraData);
44 | }
45 |
46 | for (Post comment : post.getComments()) {
47 | replies.add(recursive(comment));
48 | }
49 |
50 | parent.add("organization", organization);
51 | parent.add("author", author);
52 | if (replies.size() > 0) {
53 | parent.add("replies", replies);
54 | }
55 | return parent;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/HandlesUtil.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.google.gson.JsonObject;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.AccountsRepo;
7 | import com.joinhocus.horus.db.repos.OrganizationRepo;
8 | import lombok.experimental.UtilityClass;
9 | import org.bson.types.ObjectId;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.concurrent.CompletableFuture;
14 | import java.util.regex.Pattern;
15 |
16 | @UtilityClass
17 | public class HandlesUtil {
18 |
19 | public final Pattern TAG_PATTERN = Pattern.compile("@\\[(.*?)]");
20 |
21 | public CompletableFuture isHandleAvailable(String handle) {
22 | return CompletableFutures.combine(
23 | MongoDatabase.getInstance().getRepo(AccountsRepo.class).isUsernameAvailable(handle),
24 | MongoDatabase.getInstance().getRepo(OrganizationRepo.class).isHandleAvailable(handle),
25 | (resA, resB) -> !resA && !resB
26 | ).toCompletableFuture();
27 | }
28 |
29 | public CompletableFuture> getHandlesByQuery(String query) {
30 | return CompletableFutures.combine(
31 | MongoDatabase.getInstance().getRepo(AccountsRepo.class).searchByHandle(query),
32 | MongoDatabase.getInstance().getRepo(OrganizationRepo.class).searchByHandle(query),
33 | (resA, resB) -> ImmutableList.builder()
34 | .addAll(resA)
35 | .addAll(resB)
36 | .build()
37 | ).toCompletableFuture();
38 | }
39 |
40 | public CompletableFuture> getEntitiesBy(List handles) {
41 | return CompletableFutures.combine(
42 | MongoDatabase.getInstance().getRepo(AccountsRepo.class).getAllByHandles(handles),
43 | MongoDatabase.getInstance().getRepo(OrganizationRepo.class).getAllByHandles(handles),
44 | (resA, resB) -> {
45 | List ids = new ArrayList<>(resA.size() + resB.size());
46 | ids.addAll(resA);
47 | ids.addAll(resB);
48 | return ids;
49 | }
50 | ).toCompletableFuture();
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/misc/Spaces.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.misc;
2 |
3 | import com.amazonaws.auth.AWSCredentials;
4 | import com.amazonaws.auth.AWSCredentialsProvider;
5 | import com.amazonaws.auth.AWSStaticCredentialsProvider;
6 | import com.amazonaws.auth.BasicAWSCredentials;
7 | import com.amazonaws.client.builder.AwsClientBuilder;
8 | import com.amazonaws.services.s3.AmazonS3;
9 | import com.amazonaws.services.s3.AmazonS3ClientBuilder;
10 | import com.amazonaws.services.s3.model.CannedAccessControlList;
11 | import com.amazonaws.services.s3.model.ObjectMetadata;
12 | import com.amazonaws.services.s3.model.PutObjectRequest;
13 | import io.javalin.http.UploadedFile;
14 | import lombok.experimental.UtilityClass;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | import java.io.ByteArrayInputStream;
19 |
20 | @UtilityClass
21 | public class Spaces {
22 |
23 | private final Logger LOGGER = LoggerFactory.getLogger(Spaces.class);
24 | private final AWSCredentials CREDS = new BasicAWSCredentials("", "");
25 | private final AWSCredentialsProvider CRED_PROVIDER = new AWSStaticCredentialsProvider(CREDS);
26 |
27 | private final String EDGE_BASE = "";
28 |
29 | private final AmazonS3 SPACE = AmazonS3ClientBuilder
30 | .standard()
31 | .withCredentials(CRED_PROVIDER)
32 | .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(
33 | "sfo2.digitaloceanspaces.com",
34 | "sfo2"
35 | ))
36 | .build();
37 |
38 | public String uploadImage(UploadedFile file, String location, String name) throws Exception {
39 | byte[] bytes = IOUtils.readBytes(file.getContent());
40 | long length = bytes.length;
41 | ObjectMetadata meta = new ObjectMetadata();
42 | meta.setContentLength(length);
43 | meta.setContentType(file.getContentType());
44 | String path = "images/" + location + "/" + name + file.getExtension();
45 |
46 | // we do a new array here because it's already read from the stream
47 | PutObjectRequest request = new PutObjectRequest("hocus-media", path, new ByteArrayInputStream(bytes), meta)
48 | .withCannedAcl(CannedAccessControlList.PublicRead);
49 | SPACE.putObject(request);
50 | return EDGE_BASE + path;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/login/AccountLoginHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.login;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.AccountAuth;
5 | import com.joinhocus.horus.account.UserNames;
6 | import com.joinhocus.horus.db.MongoDatabase;
7 | import com.joinhocus.horus.db.repos.AccountsRepo;
8 | import com.joinhocus.horus.http.DefinedTypesHandler;
9 | import com.joinhocus.horus.http.Response;
10 | import com.joinhocus.horus.http.routes.account.login.model.AccountLoginRequest;
11 | import io.javalin.core.validation.BodyValidator;
12 | import io.javalin.http.Context;
13 | import org.slf4j.Logger;
14 |
15 | import java.util.concurrent.CompletableFuture;
16 |
17 | public class AccountLoginHandler implements DefinedTypesHandler {
18 |
19 | @Override
20 | public CompletableFuture handle(BodyValidator extends AccountLoginRequest> validator, Context context, Logger logger) throws Exception {
21 | AccountLoginRequest request = validator
22 | .check("login", req -> !Strings.isNullOrEmpty(req.getLogin()), "login cannot be empty")
23 | .check("login", req -> {
24 | if (UserNames.NAME_PATTERN.matcher(req.getLogin()).find()) {
25 | return true;
26 | }
27 |
28 | return AccountAuth.isValidEmail(req.getLogin());
29 | }, "login field must be an email or username")
30 | .check("password", req -> !Strings.isNullOrEmpty(req.getPassword()), "password cannot be empty")
31 | .get();
32 | AccountsRepo repo = MongoDatabase.getInstance().getRepo(AccountsRepo.class);
33 | return repo.findBasicByLogin(
34 | request.getLogin()
35 | ).thenApply(account -> {
36 | if (account == null) {
37 | return Response.of(Response.Type.BAD_REQUEST).setMessage("Invalid login or password provided");
38 | }
39 |
40 | if (!account.passwordsMatch(request.getPassword())) {
41 | return Response.of(Response.Type.FORBIDDEN).setMessage("Invalid login or password provided");
42 | }
43 |
44 | return Response.of(Response.Type.OKAY).append("token", AccountAuth.getToken(account));
45 | });
46 | }
47 |
48 | @Override
49 | public Class extends AccountLoginRequest> requestClass() {
50 | return AccountLoginRequest.class;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/organization/settings/logo/UpdateOrganizationLogoHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.organization.settings.logo;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.OrganizationRepo;
7 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.model.EmptyRequest;
10 | import com.joinhocus.horus.misc.MongoIds;
11 | import io.javalin.core.validation.BodyValidator;
12 | import io.javalin.http.Context;
13 | import io.javalin.http.UploadedFile;
14 | import org.bson.types.ObjectId;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.concurrent.CompletableFuture;
18 |
19 | public class UpdateOrganizationLogoHandler implements DefinedTypesWithUserHandler {
20 | @Override
21 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
22 | String id = context.queryParam("orgId");
23 | if (Strings.isNullOrEmpty(id)) {
24 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("orgId cannot be an empty query param"));
25 | }
26 | ObjectId orgId = MongoIds.parseId(id);
27 | if (orgId == null) {
28 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("Invalid organization id provided"));
29 | }
30 | UploadedFile file = context.uploadedFile("file");
31 | if (file == null) {
32 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("file cannot be empty"));
33 | }
34 |
35 | return MongoDatabase.getInstance().getRepo(OrganizationRepo.class).isMemberOf(orgId, account.getId()).thenCompose(isMember -> {
36 | if (!isMember) {
37 | return wrap(Response.of(Response.Type.UNAUTHORIZED));
38 | }
39 |
40 | return MongoDatabase.getInstance().getRepo(OrganizationRepo.class).setOrganizationLogo(
41 | orgId,
42 | file
43 | ).thenApply(success -> {
44 | if (!success) {
45 | return Response.of(Response.Type.BAD_REQUEST).setMessage("failed to update organization logo");
46 | }
47 |
48 | return Response.of(Response.Type.OKAY);
49 | });
50 | });
51 | }
52 |
53 | @Override
54 | public Class extends EmptyRequest> requestClass() {
55 | return EmptyRequest.class;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/post/impl/BasicPost.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.post.impl;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.organization.Organization;
5 | import com.joinhocus.horus.post.Post;
6 | import com.joinhocus.horus.post.PostPrivacy;
7 | import com.joinhocus.horus.post.PostType;
8 | import lombok.ToString;
9 | import org.bson.types.ObjectId;
10 |
11 | import java.util.Date;
12 | import java.util.List;
13 |
14 | @ToString
15 | public class BasicPost implements Post {
16 |
17 | private final ObjectId id;
18 | private final PostType type;
19 | private final PostPrivacy privacy;
20 | private final String content;
21 | private final UserAccount author;
22 | private final Organization organization;
23 | private final Date postTime;
24 | private final ObjectId parent;
25 | private final List comments;
26 |
27 | public BasicPost(
28 | ObjectId id,
29 | PostType type,
30 | PostPrivacy privacy,
31 | String content,
32 | UserAccount author,
33 | Organization organization,
34 | Date postTime,
35 | ObjectId parent,
36 | List comments
37 | ) {
38 | this.id = id;
39 | this.type = type;
40 | this.privacy = privacy;
41 | this.content = content;
42 | this.comments = comments;
43 | this.author = author;
44 | this.organization = organization;
45 | this.postTime = postTime;
46 | this.parent = parent;
47 | }
48 |
49 | @Override
50 | public ObjectId getId() {
51 | return this.id;
52 | }
53 |
54 | @Override
55 | public PostType type() {
56 | return this.type;
57 | }
58 |
59 | @Override
60 | public PostPrivacy privacy() {
61 | return this.privacy;
62 | }
63 |
64 | @Override
65 | public String content() {
66 | return this.content;
67 | }
68 |
69 | @Override
70 | public UserAccount author() {
71 | return this.author;
72 | }
73 |
74 | @Override
75 | public Organization organization() {
76 | return this.organization;
77 | }
78 |
79 | @Override
80 | public Date postTime() {
81 | return this.postTime;
82 | }
83 |
84 | @Override
85 | public ObjectId parent() {
86 | return this.parent;
87 | }
88 |
89 | @Override
90 | public List getComments() {
91 | return comments;
92 | }
93 |
94 | @Override
95 | public void addComment(Post comment) {
96 | this.comments.add(comment);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/validate/EmailValidateWaitlistHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.validate;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.db.MongoDatabase;
5 | import com.joinhocus.horus.db.repos.EmailInviteVerificationRepo;
6 | import com.joinhocus.horus.db.repos.InvitesRepo;
7 | import com.joinhocus.horus.http.DefinedTypesHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.routes.waitlist.validate.model.EmailValidateWaitListRequest;
10 | import com.joinhocus.horus.misc.MongoIds;
11 | import io.javalin.core.validation.BodyValidator;
12 | import io.javalin.http.Context;
13 | import org.bson.types.ObjectId;
14 | import org.slf4j.Logger;
15 |
16 | import java.util.concurrent.CompletableFuture;
17 |
18 | public class EmailValidateWaitlistHandler implements DefinedTypesHandler {
19 | @Override
20 | public CompletableFuture handle(BodyValidator extends EmailValidateWaitListRequest> validator, Context context, Logger logger) throws Exception {
21 | EmailValidateWaitListRequest request = validator
22 | .check("code", req -> !Strings.isNullOrEmpty(req.getCode()), "code cannot be empty")
23 | .get();
24 | return MongoDatabase.getInstance()
25 | .getRepo(EmailInviteVerificationRepo.class)
26 | .getByCode(request.getCode())
27 | .thenCompose(document -> {
28 | if (document == null) {
29 | return wrap(Response.of(Response.Type.OKAY).append("accepted", false));
30 | }
31 |
32 | String inviteId = document.getString("inviteId");
33 | return runInvitePipeline(inviteId);
34 | });
35 | }
36 |
37 | private CompletableFuture runInvitePipeline(String inviteId) {
38 | ObjectId id = MongoIds.parseId(inviteId);
39 | if (id == null) {
40 | return wrap(Response.of(Response.Type.OKAY).append("accepted", false));
41 | }
42 | return MongoDatabase.getInstance().getRepo(InvitesRepo.class)
43 | .getInviteById(id)
44 | .thenCompose(document -> {
45 | if (document == null) {
46 | return wrap(Response.of(Response.Type.OKAY).append("accepted", false));
47 | }
48 | return wrap(Response.of(Response.Type.OKAY).append("accepted", true).append("code", document.getString("code")));
49 | });
50 | }
51 |
52 | @Override
53 | public Class extends EmailValidateWaitListRequest> requestClass() {
54 | return EmailValidateWaitListRequest.class;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/waitlist/verify/VerifyWaitlistTwitterHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.waitlist.verify;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.db.MongoDatabase;
5 | import com.joinhocus.horus.db.repos.WaitListRepo;
6 | import com.joinhocus.horus.http.DefinedTypesHandler;
7 | import com.joinhocus.horus.http.Response;
8 | import com.joinhocus.horus.http.routes.waitlist.WaitlistUtil;
9 | import com.joinhocus.horus.http.routes.waitlist.verify.model.VerifyTwitterOAuthRequest;
10 | import com.joinhocus.horus.twitter.TwitterAPI;
11 | import com.joinhocus.horus.twitter.oauth.OAuthAccountResponse;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.slf4j.Logger;
15 |
16 | import java.util.concurrent.CompletableFuture;
17 |
18 | public class VerifyWaitlistTwitterHandler implements DefinedTypesHandler {
19 |
20 | @Override
21 | public CompletableFuture handle(BodyValidator extends VerifyTwitterOAuthRequest> validator, Context context, Logger logger) throws Exception {
22 | VerifyTwitterOAuthRequest request = validator
23 | .check("token", req -> !Strings.isNullOrEmpty(req.getToken()), "token cannot be empty")
24 | .check("verify", req -> !Strings.isNullOrEmpty(req.getVerify()), "verify cannot be empty")
25 | .get();
26 |
27 | return TwitterAPI.OAUTH.getOauthToken(request.getToken(), request.getVerify()).thenCompose(this::handleExists);
28 | }
29 |
30 | private CompletableFuture handleExists(OAuthAccountResponse response) {
31 | return MongoDatabase.getInstance().getRepo(WaitListRepo.class).checkIfExists(response.getUserId()).thenCompose(res -> {
32 | if (res != null) {
33 | return wrap(Response.of(Response.Type.OKAY).append("twitter", response.getName()).append("existed", true));
34 | }
35 | return joinWaitlist(response);
36 | });
37 | }
38 |
39 | public CompletableFuture joinWaitlist(OAuthAccountResponse response) {
40 | return MongoDatabase.getInstance().getRepo(WaitListRepo.class)
41 | .createWaitlistUser(response.getUserId(), response.getName())
42 | .thenApply(res -> {
43 | WaitlistUtil.sendToSlack(res.getKey().getUserId(), res.getValue()); // this gets executed in a seperate thread pool
44 | return Response.of(Response.Type.OKAY).append("twitter", res.getKey().getName()).append("joined", true);
45 | });
46 | }
47 |
48 | @Override
49 | public Class extends VerifyTwitterOAuthRequest> requestClass() {
50 | return VerifyTwitterOAuthRequest.class;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/posts/get/GetPostByIdHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.posts.get;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.db.MongoDatabase;
6 | import com.joinhocus.horus.db.repos.PostsRepo;
7 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
8 | import com.joinhocus.horus.http.Response;
9 | import com.joinhocus.horus.http.model.EmptyRequest;
10 | import com.joinhocus.horus.misc.MongoIds;
11 | import com.joinhocus.horus.post.PostUtil;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.bson.types.ObjectId;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.concurrent.CompletableFuture;
18 |
19 | public class GetPostByIdHandler implements DefinedTypesWithUserHandler {
20 | @Override
21 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
22 | String id = context.queryParam("id");
23 | boolean complex = getBooleanParam("full", context);
24 | if (Strings.isNullOrEmpty(id)) {
25 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("id cannot be empty"));
26 | }
27 | ObjectId mongoId = MongoIds.parseId(id);
28 | if (mongoId == null) {
29 | return wrap(Response.of(Response.Type.BAD_REQUEST).setMessage("malformed post id"));
30 | }
31 |
32 | if (complex) {
33 | return getFullPost(mongoId, account);
34 | }
35 |
36 | return getSimplePost(mongoId, account);
37 | }
38 |
39 | private CompletableFuture getFullPost(ObjectId id, UserAccount requester) {
40 | return MongoDatabase.getInstance().getRepo(PostsRepo.class).getByIdComplex(id, requester).thenApply(post -> {
41 | if (post == null) {
42 | return Response.of(Response.Type.NOT_FOUND);
43 | }
44 |
45 | return Response.of(Response.Type.OKAY).append("post", PostUtil.recursive(post));
46 | });
47 | }
48 |
49 | private CompletableFuture getSimplePost(ObjectId id, UserAccount requester) {
50 | return MongoDatabase.getInstance().getRepo(PostsRepo.class).getById(id, requester).thenApply(post -> {
51 | if (post == null) {
52 | return Response.of(Response.Type.NOT_FOUND);
53 | }
54 |
55 | return Response.of(Response.Type.OKAY).append("post", PostUtil.recursive(post));
56 | });
57 | }
58 |
59 | @Override
60 | public Class extends EmptyRequest> requestClass() {
61 | return EmptyRequest.class;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/setup/GetUserInviterInfo.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.setup;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.UserAccount;
5 | import com.joinhocus.horus.account.invite.Invite;
6 | import com.joinhocus.horus.db.MongoDatabase;
7 | import com.joinhocus.horus.db.repos.AccountsRepo;
8 | import com.joinhocus.horus.db.repos.InvitesRepo;
9 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
10 | import com.joinhocus.horus.http.Response;
11 | import com.joinhocus.horus.http.model.EmptyRequest;
12 | import io.javalin.core.validation.BodyValidator;
13 | import io.javalin.http.Context;
14 | import org.bson.Document;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.concurrent.CompletableFuture;
18 |
19 | public class GetUserInviterInfo implements DefinedTypesWithUserHandler {
20 |
21 | private final Response HORRIS_INVITE = Response.of(Response.Type.OKAY).append("inviter", "horris").append("org", "Hocus");
22 |
23 | @Override
24 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
25 | Document document = account.getDocument();
26 | String claimedCode = document.getString("acceptedCode");
27 | if (Strings.isNullOrEmpty(claimedCode)) {
28 | return wrap(HORRIS_INVITE);
29 | }
30 | return runPipeline(claimedCode);
31 | }
32 |
33 | private CompletableFuture runPipeline(String code) {
34 | return MongoDatabase.getInstance().getRepo(InvitesRepo.class).getInviteByCode(code).thenCompose(invite -> {
35 | if (invite == null) {
36 | return wrap(HORRIS_INVITE);
37 | }
38 | if (invite.getInviter() == null) {
39 | return wrap(HORRIS_INVITE);
40 | }
41 | return runSecondary(invite);
42 | });
43 | }
44 |
45 | private CompletableFuture runSecondary(Invite invite) {
46 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class).findById(invite.getInviter())
47 | .thenCompose(account -> {
48 | if (account == null) {
49 | return wrap(HORRIS_INVITE);
50 | }
51 |
52 | return getOrganizationInfo(invite, account);
53 | });
54 | }
55 |
56 | private CompletableFuture getOrganizationInfo(Invite invite, UserAccount inviter) {
57 | // TODO: do org stuff
58 | return wrap(Response.of(Response.Type.OKAY).append("inviter", inviter.getUsernames().getDisplay()).append("org", invite.getOrgId()));
59 | }
60 |
61 | @Override
62 | public Class extends EmptyRequest> requestClass() {
63 | return EmptyRequest.class;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/account/forgotpw/RequestPasswordResetHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.account.forgotpw;
2 |
3 | import com.google.common.base.Strings;
4 | import com.joinhocus.horus.account.AccountAuth;
5 | import com.joinhocus.horus.account.UserAccount;
6 | import com.joinhocus.horus.account.UserNames;
7 | import com.joinhocus.horus.db.MongoDatabase;
8 | import com.joinhocus.horus.db.repos.AccountsRepo;
9 | import com.joinhocus.horus.db.repos.PasswordResetRepo;
10 | import com.joinhocus.horus.http.DefinedTypesHandler;
11 | import com.joinhocus.horus.http.Response;
12 | import com.joinhocus.horus.http.routes.account.forgotpw.model.ForgotPasswordResetRequest;
13 | import io.javalin.core.validation.BodyValidator;
14 | import io.javalin.http.Context;
15 | import org.slf4j.Logger;
16 |
17 | import java.util.UUID;
18 | import java.util.concurrent.CompletableFuture;
19 |
20 | public class RequestPasswordResetHandler implements DefinedTypesHandler {
21 | @Override
22 | public CompletableFuture handle(BodyValidator extends ForgotPasswordResetRequest> validator, Context context, Logger logger) throws Exception {
23 | ForgotPasswordResetRequest request = validator
24 | .check("login", req -> !Strings.isNullOrEmpty(req.getLogin()), "login cannot be empty")
25 | .check("login", req -> {
26 | if (UserNames.NAME_PATTERN.matcher(req.getLogin()).find()) {
27 | return true;
28 | }
29 |
30 | return AccountAuth.isValidEmail(req.getLogin());
31 | }, "login field must be an email or username")
32 | .get();
33 | return MongoDatabase.getInstance().getRepo(AccountsRepo.class).findBasicByLogin(request.getLogin()).thenCompose(account -> {
34 | if (account == null) {
35 | return wrap(Response.of(Response.Type.OKAY));
36 | }
37 |
38 | UUID uuid = UUID.randomUUID();
39 | return createResetCode(account, uuid, context.ip());
40 | });
41 | }
42 |
43 | private CompletableFuture createResetCode(UserAccount account, UUID uuid, String ipAddress) {
44 | return MongoDatabase.getInstance().getRepo(PasswordResetRepo.class)
45 | .createResetCode(account, uuid, ipAddress)
46 | .thenCompose(opt -> {
47 | return sendEmail();
48 | }).thenApply(aVoid -> {
49 | return Response.of(Response.Type.OKAY);
50 | });
51 | }
52 |
53 | private CompletableFuture sendEmail() {
54 | // todo
55 | return CompletableFuture.completedFuture(null);
56 | }
57 |
58 | @Override
59 | public Class extends ForgotPasswordResetRequest> requestClass() {
60 | return ForgotPasswordResetRequest.class;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/Response.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http;
2 |
3 | import com.google.gson.JsonElement;
4 | import com.google.gson.JsonObject;
5 | import lombok.Getter;
6 |
7 | @Getter
8 | public class Response {
9 |
10 | private final int code;
11 | private String message;
12 | private final boolean success;
13 |
14 | private final JsonObject object;
15 |
16 | private Response(Type type) {
17 | this.code = type.code;
18 | this.message = type.message;
19 | this.success = type.success;
20 |
21 | this.object = new JsonObject();
22 | this.object.addProperty("status_code", this.code);
23 | this.object.addProperty("success", this.success);
24 | this.object.addProperty("message", this.message);
25 | }
26 |
27 | public static Response of(Type type) {
28 | return new Response(type);
29 | }
30 |
31 | public Response append(String key, String value) {
32 | this.object.addProperty(key, value);
33 | return this;
34 | }
35 |
36 | public Response append(String key, Number value) {
37 | this.object.addProperty(key, value);
38 | return this;
39 | }
40 |
41 | public Response append(String key, boolean value) {
42 | this.object.addProperty(key, value);
43 | return this;
44 | }
45 |
46 | public Response append(String key, JsonElement value) {
47 | this.object.add(key, value);
48 | return this;
49 | }
50 |
51 | public Response setMessage(String message) {
52 | this.message = message;
53 | this.object.addProperty("message", message);
54 | return this;
55 | }
56 |
57 | public JsonObject toJSON() {
58 | return this.object;
59 | }
60 |
61 | public enum Type {
62 | UNAUTHORIZED(401, "Unauthorized"),
63 | FORBIDDEN(403, "Forbidden"),
64 | NOT_FOUND(404, "Not Found"),
65 | RATE_LIMIT(420, "Too many requests. Rate limit exceeded"),
66 | BAD_REQUEST(400, "Bad Request"),
67 | INTERNAL_SERVER_EXCEPTION(500, "Internal server error"),
68 | OKAY(200, "Successful", true),
69 | OKAY_CREATED(201, "Successful", true),
70 |
71 | BETA_FEATURE(418, "This is a beta feature"),
72 | FEATURE_DISABLED(419, "This feature is temporarily disabled");
73 |
74 | int code;
75 | String message;
76 | boolean success;
77 |
78 | Type(int code, String message, boolean success) {
79 | this.code = code;
80 | this.message = message;
81 | this.success = success;
82 | }
83 |
84 | Type(int code, String message) {
85 | this(code, message, false);
86 | }
87 |
88 | public int getStatusCode() {
89 | return code;
90 | }
91 |
92 | public String getMessage() {
93 | return message;
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/http/routes/invites/get/GetCreatedInvitesHandler.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.http.routes.invites.get;
2 |
3 | import com.google.common.base.Strings;
4 | import com.google.gson.JsonArray;
5 | import com.google.gson.JsonObject;
6 | import com.joinhocus.horus.account.UserAccount;
7 | import com.joinhocus.horus.account.invite.Invite;
8 | import com.joinhocus.horus.db.MongoDatabase;
9 | import com.joinhocus.horus.db.repos.InvitesRepo;
10 | import com.joinhocus.horus.http.DefinedTypesWithUserHandler;
11 | import com.joinhocus.horus.http.Response;
12 | import com.joinhocus.horus.http.model.EmptyRequest;
13 | import com.joinhocus.horus.misc.CompletableFutures;
14 | import com.joinhocus.horus.twitter.TwitterAPI;
15 | import io.javalin.core.validation.BodyValidator;
16 | import io.javalin.http.Context;
17 | import org.slf4j.Logger;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | public class GetCreatedInvitesHandler implements DefinedTypesWithUserHandler {
24 | @Override
25 | public CompletableFuture handle(UserAccount account, BodyValidator extends EmptyRequest> validator, Context context, Logger logger) throws Exception {
26 | return MongoDatabase.getInstance().getRepo(InvitesRepo.class).getInvitesFrom(account.getId()).thenCompose(in -> {
27 | List> futureInvites = new ArrayList<>();
28 | for (Invite invite : in) {
29 | if (!Strings.isNullOrEmpty(invite.getTwitterId())) {
30 | futureInvites.add(TwitterAPI.fetchUserInfo(invite.getTwitterId()).thenApply(user -> {
31 | JsonObject out = new JsonObject();
32 | out.addProperty("display", "@" + user.getHandle());
33 | out.addProperty("claimed", invite.isWasClaimed());
34 |
35 | return out;
36 | }));
37 | } else {
38 | futureInvites.add(CompletableFuture.completedFuture(emailInviteToJson(invite)));
39 | }
40 | }
41 |
42 | return CompletableFutures.asList(futureInvites);
43 | }).thenApply(in -> {
44 | JsonArray invites = new JsonArray();
45 | for (JsonObject invite : in) {
46 | invites.add(invite);
47 | }
48 |
49 | return Response.of(Response.Type.OKAY).append("invites", invites);
50 | });
51 | }
52 |
53 | private JsonObject emailInviteToJson(Invite invite) {
54 | JsonObject object = new JsonObject();
55 | object.addProperty("display", invite.getEmail());
56 | object.addProperty("claimed", invite.isWasClaimed());
57 |
58 | return object;
59 | }
60 |
61 | @Override
62 | public Class extends EmptyRequest> requestClass() {
63 | return EmptyRequest.class;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/joinhocus/horus/organization/Organization.java:
--------------------------------------------------------------------------------
1 | package com.joinhocus.horus.organization;
2 |
3 | import com.joinhocus.horus.account.UserAccount;
4 | import com.joinhocus.horus.misc.follow.Followable;
5 | import org.bson.types.ObjectId;
6 |
7 | import java.util.Map;
8 | import java.util.concurrent.CompletableFuture;
9 |
10 | public interface Organization extends Followable {
11 |
12 | ObjectId getId();
13 |
14 | String getName();
15 |
16 | String getHandle();
17 |
18 | OrganizationSettings getSettings();
19 |
20 | Map getSeats();
21 |
22 | CompletableFuture