Skip to content

Commit

Permalink
feat: dashboard search support (#72)
Browse files Browse the repository at this point in the history
* initial commit

* query fixes

* query fix

* upadtes CHANGELOG and version info

* updates pluginInterfaceSupported
  • Loading branch information
jscyo authored Mar 31, 2023
1 parent 05e630f commit af32d11
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.4.0] - 2023-03-30

- Support for Dashboard Search

## [2.3.0] - 2023-03-27
- Support for TOTP recipe
- Support for active users
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
}

version = "2.3.0"
version = "2.4.0"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion pluginInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": "contains a list of plugin interfaces branch names that this core supports",
"versions": [
"2.21"
"2.22"
]
}
5 changes: 3 additions & 2 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.gson.JsonObject;
import io.supertokens.pluginInterface.*;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo;
import io.supertokens.pluginInterface.dashboard.DashboardUser;
import io.supertokens.pluginInterface.dashboard.exceptions.UserIdNotFoundException;
Expand Down Expand Up @@ -1146,10 +1147,10 @@ public long getUsersCount(RECIPE_ID[] includeRecipeIds) throws StorageQueryExcep
@Override
public AuthRecipeUserInfo[] getUsers(@NotNull Integer limit, @NotNull String timeJoinedOrder,
@Nullable RECIPE_ID[] includeRecipeIds, @Nullable String userId,
@Nullable Long timeJoined)
@Nullable Long timeJoined, @Nullable DashboardSearchTags dashboardSearchTags)
throws StorageQueryException {
try {
return GeneralQueries.getUsers(this, limit, timeJoinedOrder, includeRecipeIds, userId, timeJoined);
return GeneralQueries.getUsers(this, limit, timeJoinedOrder, includeRecipeIds, userId, timeJoined, dashboardSearchTags);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.RowMapper;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.storage.postgresql.ConnectionPool;
import io.supertokens.storage.postgresql.Start;
Expand Down Expand Up @@ -125,7 +126,7 @@ public static void createTablesIfNotExists(Start start) throws SQLException, Sto
getInstance(start).addState(CREATING_NEW_TABLE, null);
update(start, ActiveUsersQueries.getQueryToCreateUserLastActiveTable(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getAccessTokenSigningKeysTable())) {
getInstance(start).addState(CREATING_NEW_TABLE, null);
update(start, getQueryToCreateAccessTokenSigningKeysTable(start), NO_OP_SETTER);
Expand Down Expand Up @@ -396,13 +397,172 @@ public static boolean doesUserIdExist(Start start, String userId) throws SQLExce
}

public static AuthRecipeUserInfo[] getUsers(Start start, @NotNull Integer limit, @NotNull String timeJoinedOrder,
@Nullable RECIPE_ID[] includeRecipeIds, @Nullable String userId, @Nullable Long timeJoined)
@Nullable RECIPE_ID[] includeRecipeIds, @Nullable String userId, @Nullable Long timeJoined,
@Nullable DashboardSearchTags dashboardSearchTags)
throws SQLException, StorageQueryException {

// This list will be used to keep track of the result's order from the db
List<UserInfoPaginationResultHolder> usersFromQuery;

{
if (dashboardSearchTags != null) {
ArrayList<String> queryList = new ArrayList<>();
{
StringBuilder USER_SEARCH_TAG_CONDITION = new StringBuilder();

{
// check if we should search through the emailpassword table
if (dashboardSearchTags.shouldEmailPasswordTableBeSearched()) {
String QUERY = "SELECT allAuthUsersTable.*" + " FROM " + getConfig(start).getUsersTable()
+ " AS allAuthUsersTable" +
" JOIN " + getConfig(start).getEmailPasswordUsersTable()
+ " AS emailpasswordTable ON allAuthUsersTable.user_id = emailpasswordTable.user_id";

// attach email tags to queries
QUERY = QUERY + " WHERE emailpasswordTable.email LIKE ? OR emailpasswordTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(0) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(0) + "%");
for (int i = 1; i < dashboardSearchTags.emails.size(); i++) {
QUERY += " OR emailpasswordTable.email LIKE ? OR emailpasswordTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(i) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(i) + "%");
}

USER_SEARCH_TAG_CONDITION.append("SELECT * FROM ( ").append(QUERY).append(" LIMIT 1000) AS emailpasswordResultTable");
}
}

{
// check if we should search through the thirdparty table
if (dashboardSearchTags.shouldThirdPartyTableBeSearched()) {
String QUERY = "SELECT allAuthUsersTable.*" + " FROM " + getConfig(start).getUsersTable()
+ " AS allAuthUsersTable" +
" JOIN " + getConfig(start).getThirdPartyUsersTable()
+ " AS thirdPartyTable ON allAuthUsersTable.user_id = thirdPartyTable.user_id";

// check if email tag is present
if (dashboardSearchTags.emails != null) {

QUERY += " WHERE thirdPartyTable.email LIKE ? OR thirdPartyTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(0) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(0) + "%");

for (int i = 1; i < dashboardSearchTags.emails.size(); i++) {
QUERY += " OR thirdPartyTable.email LIKE ? OR thirdPartyTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(i) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(i) + "%");
}

}

// check if providers tag is present
if (dashboardSearchTags.providers != null) {
if (dashboardSearchTags.emails != null) {
QUERY += " AND ";
} else {
QUERY += " WHERE ";
}

QUERY += " thirdPartyTable.third_party_id LIKE ?";
queryList.add(dashboardSearchTags.providers.get(0) + "%");
for (int i = 1; i < dashboardSearchTags.providers.size(); i++) {
QUERY += " OR thirdPartyTable.third_party_id LIKE ?";
queryList.add(dashboardSearchTags.providers.get(i) + "%");
}
}

// check if we need to append this to an existing search query
if (USER_SEARCH_TAG_CONDITION.length() != 0) {
USER_SEARCH_TAG_CONDITION.append(" UNION ").append("SELECT * FROM ( ").append(QUERY)
.append(" LIMIT 1000) AS thirdPartyResultTable");

} else {
USER_SEARCH_TAG_CONDITION.append("SELECT * FROM ( ").append(QUERY).append(" LIMIT 1000) AS thirdPartyResultTable");

}
}
}

{
// check if we should search through the passwordless table
if (dashboardSearchTags.shouldPasswordlessTableBeSearched()) {
String QUERY = "SELECT allAuthUsersTable.*" + " FROM " + getConfig(start).getUsersTable()
+ " AS allAuthUsersTable" +
" JOIN " + getConfig(start).getPasswordlessUsersTable()
+ " AS passwordlessTable ON allAuthUsersTable.user_id = passwordlessTable.user_id";

// check if email tag is present
if (dashboardSearchTags.emails != null) {

QUERY = QUERY + " WHERE passwordlessTable.email LIKE ? OR passwordlessTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(0) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(0) + "%");
for (int i = 1; i < dashboardSearchTags.emails.size(); i++) {
QUERY += " OR passwordlessTable.email LIKE ? OR passwordlessTable.email LIKE ?";
queryList.add(dashboardSearchTags.emails.get(i) + "%");
queryList.add("%@" + dashboardSearchTags.emails.get(i) + "%");
}
}

// check if phone tag is present
if (dashboardSearchTags.phoneNumbers != null) {

if (dashboardSearchTags.emails != null) {
QUERY += " AND ";
} else {
QUERY += " WHERE ";
}

QUERY += " passwordlessTable.phone_number LIKE ?";
if (dashboardSearchTags.phoneNumbers.get(0).startsWith("+")) {
queryList.add(dashboardSearchTags.phoneNumbers.get(0) + "%");
} else {
queryList.add("+" + dashboardSearchTags.phoneNumbers.get(0) + "%");
}
for (int i = 1; i < dashboardSearchTags.phoneNumbers.size(); i++) {
QUERY += " OR passwordlessTable.phone_number LIKE ?";
if (dashboardSearchTags.phoneNumbers.get(i).startsWith("+")) {
queryList.add(dashboardSearchTags.phoneNumbers.get(i) + "%");
} else {
queryList.add("+" + dashboardSearchTags.phoneNumbers.get(i) + "%");
}
}
}

// check if we need to append this to an existing search query
if (USER_SEARCH_TAG_CONDITION.length() != 0) {
USER_SEARCH_TAG_CONDITION.append(" UNION ").append("SELECT * FROM ( ").append(QUERY)
.append(" LIMIT 1000) AS passwordlessResultTable");

} else {
USER_SEARCH_TAG_CONDITION.append("SELECT * FROM ( ").append(QUERY).append(" LIMIT 1000) AS passwordlessResultTable");

}
}
}

if (USER_SEARCH_TAG_CONDITION.toString().length() == 0) {
usersFromQuery = new ArrayList<>();
} else {

String finalQuery = "SELECT * FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )"
+ " AS finalResultTable ORDER BY time_joined " + timeJoinedOrder + ", user_id DESC ";
usersFromQuery = execute(start, finalQuery, pst -> {
for (int i = 1; i <= queryList.size(); i++) {
pst.setString(i, queryList.get(i - 1));
}
}, result -> {
List<UserInfoPaginationResultHolder> temp = new ArrayList<>();
while (result.next()) {
temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"),
result.getString("recipe_id")));
}
return temp;
});
}

}

} else {
StringBuilder RECIPE_ID_CONDITION = new StringBuilder();
if (includeRecipeIds != null && includeRecipeIds.length > 0) {
RECIPE_ID_CONDITION.append("recipe_id IN (");
Expand Down

0 comments on commit af32d11

Please sign in to comment.