Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add an api to add bulk import users #927

Closed
wants to merge 16 commits into from
79 changes: 79 additions & 0 deletions src/main/java/io/supertokens/bulkimport/BulkImport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.supertokens.bulkimport;

import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.utils.Utils;

import com.google.gson.JsonObject;

import java.util.ArrayList;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class BulkImport {

public static final int GET_USERS_PAGINATION_LIMIT = 500;
public static final int GET_USERS_DEFAULT_LIMIT = 100;

public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, ArrayList<BulkImportUser> users)
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
throws StorageQueryException, TenantOrAppNotFoundException {
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
while (true) {
try {
appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users);
break;
} catch (io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException ignored) {
// We re-generate the user id for every user and retry
for (BulkImportUser user : users) {
user.id = Utils.getUUID();
}
}
}
}

public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage,
@Nonnull Integer limit, @Nullable String status, @Nullable String paginationToken)
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException,
TenantOrAppNotFoundException {
JsonObject[] users;

if (paginationToken == null) {
users = appIdentifierWithStorage.getBulkImportStorage()
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null);
} else {
BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken);
users = appIdentifierWithStorage.getBulkImportStorage()
.getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId);
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
}

String nextPaginationToken = null;
int maxLoop = users.length;
if (users.length == limit + 1) {
maxLoop = limit;
nextPaginationToken = new BulkImportUserPaginationToken(users[limit].get("id").getAsString())
.generateToken();
}

JsonObject[] resultUsers = new JsonObject[maxLoop];
System.arraycopy(users, 0, resultUsers, 0, maxLoop);
return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.supertokens.bulkimport;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.gson.JsonObject;

public class BulkImportUserPaginationContainer {
public final JsonObject[] users;
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
public final String nextPaginationToken;

public BulkImportUserPaginationContainer(@Nonnull JsonObject[] users, @Nullable String nextPaginationToken) {
this.users = users;
this.nextPaginationToken = nextPaginationToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.supertokens.bulkimport;

import java.util.Base64;

public class BulkImportUserPaginationToken {
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
public final String bulkImportUserId;

public BulkImportUserPaginationToken(String bulkImportUserId) {
this.bulkImportUserId = bulkImportUserId;
}

public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException {
try {
String bulkImportUserId = new String(Base64.getDecoder().decode(token));
return new BulkImportUserPaginationToken(bulkImportUserId);
} catch (Exception e) {
throw new InvalidTokenException();
}
}

public String generateToken() {
return new String(Base64.getEncoder().encode((this.bulkImportUserId).getBytes()));
}

public static class InvalidTokenException extends Exception {

private static final long serialVersionUID = 6289026174830695478L;
}
}
12 changes: 11 additions & 1 deletion src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
import io.supertokens.pluginInterface.bulkimport.BulkImportStorage;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo;
import io.supertokens.pluginInterface.dashboard.DashboardUser;
Expand Down Expand Up @@ -102,7 +104,7 @@ public class Start
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage,
DashboardSQLStorage, AuthRecipeSQLStorage {
DashboardSQLStorage, AuthRecipeSQLStorage, BulkImportStorage {

private static final Object appenderLock = new Object();
private static final String APP_ID_KEY_NAME = "app_id";
Expand Down Expand Up @@ -2952,4 +2954,12 @@ public UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, A
}
}

@Override
public void addBulkImportUsers(AppIdentifier appIdentifier, ArrayList<BulkImportUser> users) throws StorageQueryException {
try {
BulkImportQueries.insertBulkImportUsers(this, users);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,8 @@ public String getDashboardUsersTable() {
public String getDashboardSessionsTable() {
return "dashboard_user_sessions";
}

public String getBulkImportUsersTable() {
return "bulk_import_users";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/


package io.supertokens.inmemorydb.queries;

import io.supertokens.inmemorydb.config.Config;
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;

import static io.supertokens.inmemorydb.PreparedStatementValueSetter.NO_OP_SETTER;
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;

import java.sql.SQLException;
import java.util.ArrayList;

import io.supertokens.inmemorydb.Start;

public class BulkImportQueries {
static String getQueryToCreateBulkImportUsersTable(Start start) {
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getBulkImportUsersTable() + " ("
+ "id CHAR(36) PRIMARY KEY,"
+ "raw_data TEXT NOT NULL,"
+ "status VARCHAR(128) NOT NULL DEFAULT 'NEW',"
+ "error_msg TEXT,"
+ "created_at TIMESTAMP DEFAULT (strftime('%s', 'now')),"
+ "updated_at TIMESTAMP DEFAULT (strftime('%s', 'now'))"
+ " );";
}

public static String getQueryToCreateStatusUpdatedAtIndex(Start start) {
return "CREATE INDEX IF NOT EXISTS bulk_import_users_status_updated_at_index ON "
+ Config.getConfig(start).getBulkImportUsersTable() + " (status, updated_at)";
}

public static void insertBulkImportUsers(Start start, ArrayList<BulkImportUser> users)
throws SQLException, StorageQueryException {
StringBuilder queryBuilder = new StringBuilder(
"INSERT INTO " + Config.getConfig(start).getBulkImportUsersTable() + " (id, raw_data) VALUES ");
for (BulkImportUser user : users) {
queryBuilder.append("('")
.append(user.id)
.append("', '")
.append(user.toString())
.append("')");

if (user != users.get(users.size() - 1)) {
queryBuilder.append(",");
}
}
queryBuilder.append(";");
update(start, queryBuilder.toString(), NO_OP_SETTER);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc
update(start, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getBulkImportUsersTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, BulkImportQueries.getQueryToCreateBulkImportUsersTable(start), NO_OP_SETTER);
// index:
update(start, BulkImportQueries.getQueryToCreateStatusUpdatedAtIndex(start), NO_OP_SETTER);
}
}


Expand Down
5 changes: 5 additions & 0 deletions src/main/java/io/supertokens/webserver/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.webserver.api.accountlinking.*;
import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers;
import io.supertokens.webserver.api.bulkimport.GetBulkImportUsers;
import io.supertokens.webserver.api.core.*;
import io.supertokens.webserver.api.dashboard.*;
import io.supertokens.webserver.api.emailpassword.UserAPI;
Expand Down Expand Up @@ -259,6 +261,9 @@ private void setupRoutes() {

addAPI(new RequestStatsAPI(main));

addAPI(new AddBulkImportUsers(main));
addAPI(new GetBulkImportUsers(main));

StandardContext context = tomcatReference.getContext();
Tomcat tomcat = tomcatReference.getTomcat();

Expand Down
Loading
Loading