From ae49853b93cf6d49207e2ea1298e590d4a343925 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 14 Feb 2024 19:26:28 +0530 Subject: [PATCH 01/10] feat: Add interfaces and types for bulk import --- .../bulkimport/BulkImportStorage.java | 28 ++ .../bulkimport/BulkImportUser.java | 316 ++++++++++++++++++ .../InvalidBulkImportDataException.java | 35 ++ .../AppIdentifierWithStorage.java | 9 + .../TenantIdentifierWithStorage.java | 9 + 5 files changed, 397 insertions(+) create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java new file mode 100644 index 00000000..5467a60e --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -0,0 +1,28 @@ +package io.supertokens.pluginInterface.bulkimport; + +import java.util.ArrayList; + +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; + +public interface BulkImportStorage extends NonAuthRecipeStorage { + /** + * Add users to the bulk_import_users table + */ + void addBulkImportUsers(AppIdentifier appIdentifier, ArrayList users) + throws StorageQueryException, TenantOrAppNotFoundException; + + /** + * Get users from the bulk_import_users table + */ + // void getBulkImportUsers(AppIdentifier appIdentifier, @Nullable String status, @Nonnull Integer limit, @Nullable String bulkImportUserId) + // throws StorageQueryException; + + /** + * Delete users by id from the bulk_import_users table + */ + // void deleteBulkImportUsers(AppIdentifier appIdentifier, @Nullable ArrayList bulkImportUserIds) + // throws StorageQueryException; +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java new file mode 100644 index 00000000..dbc03b3b --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -0,0 +1,316 @@ +/* + * 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.pluginInterface.bulkimport; + +import java.util.List; +import java.util.UUID; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; + +import java.util.ArrayList; +import java.util.Arrays; + +public class BulkImportUser { + public String id; + public JsonObject userData; + public String externalUserId; + public JsonObject userMetadata; + public List userRoles; + public List totpDevices; + public List loginMethods; + public ArrayList errors = new ArrayList<>(); + + public BulkImportUser(JsonObject userData, ArrayList validTenantIds, String id) throws InvalidBulkImportDataException { + this.id = id != null ? id : UUID.randomUUID().toString(); + this.userData = userData; + this.externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, + "."); + this.userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, + "."); + this.userRoles = getParsedUserRoles(userData); + this.totpDevices = getParsedTotpDevices(userData); + this.loginMethods = getParsedLoginMethods(userData, validTenantIds); + + if (errors.size() > 0) { + throw new InvalidBulkImportDataException(errors); + } + } + + public String toString() { + return this.userData.toString(); + } + + private ArrayList getParsedUserRoles(JsonObject userData) { + JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING, false, + JsonArray.class, "."); + + if (jsonUserRoles == null) { + return null; + } + + ArrayList userRoles = new ArrayList<>(); + jsonUserRoles.forEach(role -> userRoles.add(role.getAsString())); + return userRoles; + } + + private ArrayList getParsedTotpDevices(JsonObject userData) { + JsonArray jsonTotpDevices = parseAndValidateField(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, + JsonArray.class, "."); + if (jsonTotpDevices == null) { + return null; + } + + ArrayList totpDevices = new ArrayList<>(); + for (JsonElement jsonTotpDevice : jsonTotpDevices) { + totpDevices.add(new TotpDevice(jsonTotpDevice.getAsJsonObject())); + } + return totpDevices; + } + + private ArrayList getParsedLoginMethods(JsonObject userData, ArrayList validTenantIds) { + JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, + JsonArray.class, "."); + + if (jsonLoginMethods == null) { + return new ArrayList<>(); + } + + if (jsonLoginMethods.size() == 0) { + errors.add("At least one loginMethod is required."); + return new ArrayList<>(); + } + + Boolean hasPrimaryLoginMethod = false; + + ArrayList loginMethods = new ArrayList<>(); + for (JsonElement jsonLoginMethod : jsonLoginMethods) { + JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); + + if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { + if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { + if (hasPrimaryLoginMethod) { + errors.add("No two loginMethods can have isPrimary as true."); + } + hasPrimaryLoginMethod = true; + } + } + + loginMethods.add(new LoginMethod(jsonLoginMethodObj)); + } + + return loginMethods; + } + + @SuppressWarnings("unchecked") + private T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, boolean isRequired, + Class targetType, String errorSuffix) { + if (jsonObject.has(key)) { + if (validateJsonFieldType(jsonObject, key, expectedType)) { + T value; + switch (expectedType) { + case STRING: + value = (T) jsonObject.get(key).getAsString(); + break; + case NUMBER: + value = (T) jsonObject.get(key).getAsNumber(); + break; + case BOOLEAN: + Boolean boolValue = jsonObject.get(key).getAsBoolean(); + value = (T) boolValue; + break; + case OBJECT: + value = (T) jsonObject.get(key).getAsJsonObject(); + break; + case ARRAY_OF_OBJECT, ARRAY_OF_STRING: + value = (T) jsonObject.get(key).getAsJsonArray(); + break; + default: + value = null; + break; + } + if (value != null) { + return targetType.cast(value); + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else if (isRequired) { + errors.add(key + " is required" + errorSuffix); + } + return null; + } + + public enum ValueType { + STRING, + NUMBER, + BOOLEAN, + OBJECT, + ARRAY_OF_STRING, + ARRAY_OF_OBJECT + } + + private String getTypeForErrorMessage(ValueType type) { + return switch (type) { + case STRING -> "string"; + case NUMBER -> "number"; + case BOOLEAN -> "boolean"; + case OBJECT -> "object"; + case ARRAY_OF_STRING -> "array of string"; + case ARRAY_OF_OBJECT -> "array of object"; + }; + } + + private Boolean validateJsonFieldType(JsonObject jsonObject, String key, ValueType expectedType) { + if (jsonObject.has(key)) { + return switch (expectedType) { + case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString(); + case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); + case OBJECT -> jsonObject.get(key).isJsonObject(); + case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() + && validateArrayElements(jsonObject.getAsJsonArray(key), expectedType); + default -> false; + }; + } + return false; + } + + private boolean validateArrayElements(JsonArray array, ValueType expectedType) { + List elements = new ArrayList<>(); + array.forEach(elements::add); + + return switch (expectedType) { + case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); + case ARRAY_OF_STRING -> + elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()); + default -> false; + }; + } + + public class TotpDevice { + public String secretKey; + public Number period; + public Number skew; + public String deviceName; + + public TotpDevice(JsonObject jsonTotpDevice) { + this.secretKey = parseAndValidateField(jsonTotpDevice, "secretKey", ValueType.STRING, true, String.class, + " for a totp device."); + this.period = parseAndValidateField(jsonTotpDevice, "period", ValueType.NUMBER, true, Number.class, + " for a totp device."); + this.skew = parseAndValidateField(jsonTotpDevice, "skew", ValueType.NUMBER, true, Number.class, + " for a totp device."); + this.deviceName = parseAndValidateField(jsonTotpDevice, "deviceName", ValueType.STRING, false, String.class, + " for a totp device."); + } + } + + public class LoginMethod { + public String tenantId; + public Boolean isVerified; + public Boolean isPrimary; + public long timeJoinedInMSSinceEpoch; + public String recipeId; + + public EmailPasswordLoginMethod emailPasswordLoginMethod; + public ThirdPartyLoginMethod thirdPartyLoginMethod; + public PasswordlessLoginMethod passwordlessLoginMethod; + + public LoginMethod(JsonObject jsonLoginMethod) { + this.recipeId = parseAndValidateField(jsonLoginMethod, "recipeId", ValueType.STRING, true, String.class, + " for a loginMethod."); + this.tenantId = parseAndValidateField(jsonLoginMethod, "tenantId", ValueType.STRING, false, String.class, + " for a loginMethod."); + this.isVerified = parseAndValidateField(jsonLoginMethod, "isVerified", ValueType.BOOLEAN, false, + Boolean.class, " for a loginMethod."); + this.isPrimary = parseAndValidateField(jsonLoginMethod, "isPrimary", ValueType.BOOLEAN, false, + Boolean.class, " for a loginMethod."); + Number timeJoined = parseAndValidateField(jsonLoginMethod, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, + false, Number.class, " for a loginMethod"); + this.timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; + + if ("emailpassword".equals(this.recipeId)) { + this.emailPasswordLoginMethod = new EmailPasswordLoginMethod(jsonLoginMethod); + } else if ("thirdparty".equals(this.recipeId)) { + this.thirdPartyLoginMethod = new ThirdPartyLoginMethod(jsonLoginMethod); + } else if ("passwordless".equals(this.recipeId)) { + this.passwordlessLoginMethod = new PasswordlessLoginMethod(jsonLoginMethod); + } else if (this.recipeId != null) { + errors.add( + "Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); + } + } + + public class EmailPasswordLoginMethod { + public String email; + public String passwordHash; + public String hashingAlgorithm; + + public EmailPasswordLoginMethod(JsonObject jsonLoginMethod) { + this.email = parseAndValidateField(jsonLoginMethod, "email", ValueType.STRING, true, String.class, + " for an emailpassword recipe."); + this.passwordHash = parseAndValidateField(jsonLoginMethod, "passwordHash", ValueType.STRING, true, + String.class, " for an emailpassword recipe."); + this.hashingAlgorithm = parseAndValidateField(jsonLoginMethod, "hashingAlgorithm", ValueType.STRING, + true, String.class, " for an emailpassword recipe."); + + if (this.hashingAlgorithm != null && !Arrays.asList("bcrypt", "argon2", "firebase_scrypt").contains(hashingAlgorithm)) { + errors.add( + "Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); + } + } + } + + public class ThirdPartyLoginMethod { + public String email; + public String thirdPartyId; + public String thirdPartyUserId; + + public ThirdPartyLoginMethod(JsonObject jsonObject) { + this.email = parseAndValidateField(jsonObject, "email", ValueType.STRING, true, String.class, + " for a thirdparty recipe."); + this.thirdPartyId = parseAndValidateField(jsonObject, "thirdPartyId", ValueType.STRING, true, + String.class, " for a thirdparty recipe."); + this.thirdPartyUserId = parseAndValidateField(jsonObject, "thirdPartyUserId", ValueType.STRING, true, + String.class, " for a thirdparty recipe."); + } + } + + public class PasswordlessLoginMethod { + public String email; + public String phoneNumber; + + public PasswordlessLoginMethod(JsonObject jsonObject) { + this.email = parseAndValidateField(jsonObject, "email", ValueType.STRING, false, String.class, + " for a passwordless recipe."); + this.phoneNumber = parseAndValidateField(jsonObject, "phoneNumber", ValueType.STRING, false, + String.class, " for a passwordless recipe."); + + if ((email != null && email.isEmpty()) && (phoneNumber != null && phoneNumber.isEmpty())) { + errors.add( + "Either email or phoneNumber is required for a passwordless recipe."); + } + } + } + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java new file mode 100644 index 00000000..86eed1fa --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java @@ -0,0 +1,35 @@ +/* + * 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.pluginInterface.bulkimport.exceptions; + +import java.util.ArrayList; + +public class InvalidBulkImportDataException extends Exception { + private static final long serialVersionUID = 1L; + public ArrayList errors; + + public InvalidBulkImportDataException(ArrayList errors) { + super("Data has missing or invalid fields. Please check the errors field for more details."); + this.errors = errors; + } + + public void addError(String error) { + this.errors.add(error); + } +} + + diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java index 1134d51d..a2e77ae9 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java @@ -20,6 +20,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; @@ -159,4 +160,12 @@ public ActiveUsersStorage getActiveUsersStorage() { } return (ActiveUsersStorage) this.storage; } + + public BulkImportStorage getBulkImportStorage() { + if (this.storage.getType() != STORAGE_TYPE.SQL) { + // we only support SQL for now + throw new UnsupportedOperationException(""); + } + return (BulkImportStorage) this.storage; + } } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java index 82002f0c..faf24e25 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java @@ -19,6 +19,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -131,4 +132,12 @@ public MultitenancyStorage getMultitenancyStorageWithTargetStorage() { } return (MultitenancyStorage) this.storage; } + + public BulkImportStorage getBulkImportStorage() { + if (this.storage.getType() != STORAGE_TYPE.SQL) { + // we only support SQL for now + throw new UnsupportedOperationException(""); + } + return (BulkImportStorage) this.storage; + } } From 4b552908c8cad5f2061a42b3bc03f502f96e48b9 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Thu, 15 Feb 2024 23:34:52 +0530 Subject: [PATCH 02/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 13 ++++++++---- .../bulkimport/BulkImportUser.java | 6 +++--- .../exceptions/DuplicateUserIdException.java | 21 +++++++++++++++++++ .../TenantIdentifierWithStorage.java | 8 ------- 4 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index 5467a60e..05ee0e37 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -2,9 +2,13 @@ import java.util.ArrayList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; + import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; public interface BulkImportStorage extends NonAuthRecipeStorage { @@ -12,13 +16,14 @@ public interface BulkImportStorage extends NonAuthRecipeStorage { * Add users to the bulk_import_users table */ void addBulkImportUsers(AppIdentifier appIdentifier, ArrayList users) - throws StorageQueryException, TenantOrAppNotFoundException; + throws StorageQueryException, + io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException; /** * Get users from the bulk_import_users table */ - // void getBulkImportUsers(AppIdentifier appIdentifier, @Nullable String status, @Nonnull Integer limit, @Nullable String bulkImportUserId) - // throws StorageQueryException; + JsonObject[] getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable String status, + @Nullable String bulkImportUserId) throws StorageQueryException; /** * Delete users by id from the bulk_import_users table diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index dbc03b3b..6f857f53 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -38,7 +38,7 @@ public class BulkImportUser { public List loginMethods; public ArrayList errors = new ArrayList<>(); - public BulkImportUser(JsonObject userData, ArrayList validTenantIds, String id) throws InvalidBulkImportDataException { + public BulkImportUser(JsonObject userData, String id) throws InvalidBulkImportDataException { this.id = id != null ? id : UUID.randomUUID().toString(); this.userData = userData; this.externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, @@ -47,7 +47,7 @@ public BulkImportUser(JsonObject userData, ArrayList validTenantIds, Str "."); this.userRoles = getParsedUserRoles(userData); this.totpDevices = getParsedTotpDevices(userData); - this.loginMethods = getParsedLoginMethods(userData, validTenantIds); + this.loginMethods = getParsedLoginMethods(userData); if (errors.size() > 0) { throw new InvalidBulkImportDataException(errors); @@ -85,7 +85,7 @@ private ArrayList getParsedTotpDevices(JsonObject userData) { return totpDevices; } - private ArrayList getParsedLoginMethods(JsonObject userData, ArrayList validTenantIds) { + private ArrayList getParsedLoginMethods(JsonObject userData) { JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, "."); diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java new file mode 100644 index 00000000..07070c15 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java @@ -0,0 +1,21 @@ +/* + * 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.pluginInterface.bulkimport.exceptions; + +public class DuplicateUserIdException extends Exception { + private static final long serialVersionUID = 6848053563771647272L; +} diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java index faf24e25..8285312b 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java @@ -132,12 +132,4 @@ public MultitenancyStorage getMultitenancyStorageWithTargetStorage() { } return (MultitenancyStorage) this.storage; } - - public BulkImportStorage getBulkImportStorage() { - if (this.storage.getType() != STORAGE_TYPE.SQL) { - // we only support SQL for now - throw new UnsupportedOperationException(""); - } - return (BulkImportStorage) this.storage; - } } From 552226de564ee8f756902f088882c63be5f87e4f Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 20 Feb 2024 08:08:45 +0530 Subject: [PATCH 03/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 43 +-- .../bulkimport/BulkImportUser.java | 286 +++--------------- .../InvalidBulkImportDataException.java | 35 --- .../TenantIdentifierWithStorage.java | 2 - .../utils/JsonValidatorUtils.java | 116 +++++++ 5 files changed, 187 insertions(+), 295 deletions(-) delete mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java create mode 100644 src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index 05ee0e37..044acb67 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -1,6 +1,6 @@ package io.supertokens.pluginInterface.bulkimport; -import java.util.ArrayList; +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -12,22 +12,27 @@ import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; public interface BulkImportStorage extends NonAuthRecipeStorage { - /** - * Add users to the bulk_import_users table - */ - void addBulkImportUsers(AppIdentifier appIdentifier, ArrayList users) - throws StorageQueryException, - io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException; - - /** - * Get users from the bulk_import_users table - */ - JsonObject[] getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable String status, - @Nullable String bulkImportUserId) throws StorageQueryException; - - /** - * Delete users by id from the bulk_import_users table - */ - // void deleteBulkImportUsers(AppIdentifier appIdentifier, @Nullable ArrayList bulkImportUserIds) - // throws StorageQueryException; + /** + * Add users to the bulk_import_users table + */ + void addBulkImportUsers(AppIdentifier appIdentifier, List users) + throws StorageQueryException, + io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException; + + /** + * Get users from the bulk_import_users table + */ + List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, + @Nullable String bulkImportUserId) throws StorageQueryException; + + /** + * Delete users by id from the bulk_import_users table + */ + // void deleteBulkImportUsers(AppIdentifier appIdentifier, @Nullable + // ArrayList bulkImportUserIds) + // throws StorageQueryException; + + public enum BulkImportUserStatus { + NEW, PROCESSING, FAILED + } } diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index 6f857f53..7861b0bb 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -17,215 +17,55 @@ package io.supertokens.pluginInterface.bulkimport; import java.util.List; -import java.util.UUID; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; +import com.google.gson.Gson; import com.google.gson.JsonObject; -import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; - -import java.util.ArrayList; -import java.util.Arrays; - public class BulkImportUser { public String id; - public JsonObject userData; public String externalUserId; public JsonObject userMetadata; public List userRoles; public List totpDevices; public List loginMethods; - public ArrayList errors = new ArrayList<>(); - - public BulkImportUser(JsonObject userData, String id) throws InvalidBulkImportDataException { - this.id = id != null ? id : UUID.randomUUID().toString(); - this.userData = userData; - this.externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, - "."); - this.userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, - "."); - this.userRoles = getParsedUserRoles(userData); - this.totpDevices = getParsedTotpDevices(userData); - this.loginMethods = getParsedLoginMethods(userData); - - if (errors.size() > 0) { - throw new InvalidBulkImportDataException(errors); - } - } - public String toString() { - return this.userData.toString(); + public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, List userRoles, List totpDevices, List loginMethods) { + this.id = id; + this.externalUserId = externalUserId; + this.userMetadata = userMetadata; + this.userRoles = userRoles; + this.totpDevices = totpDevices; + this.loginMethods = loginMethods; } - private ArrayList getParsedUserRoles(JsonObject userData) { - JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING, false, - JsonArray.class, "."); + public String toString() { + Gson gson = new Gson(); + JsonObject json = new JsonObject(); - if (jsonUserRoles == null) { - return null; - } - - ArrayList userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> userRoles.add(role.getAsString())); - return userRoles; - } - - private ArrayList getParsedTotpDevices(JsonObject userData) { - JsonArray jsonTotpDevices = parseAndValidateField(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, - JsonArray.class, "."); - if (jsonTotpDevices == null) { - return null; - } + json.addProperty("externalUserId", externalUserId); + json.add("userMetadata", userMetadata); + json.add("roles", gson.toJsonTree(userRoles)); + json.add("totp", gson.toJsonTree(totpDevices)); + json.add("loginMethods", gson.toJsonTree(loginMethods)); - ArrayList totpDevices = new ArrayList<>(); - for (JsonElement jsonTotpDevice : jsonTotpDevices) { - totpDevices.add(new TotpDevice(jsonTotpDevice.getAsJsonObject())); - } - return totpDevices; + return json.toString(); } - private ArrayList getParsedLoginMethods(JsonObject userData) { - JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, - JsonArray.class, "."); - - if (jsonLoginMethods == null) { - return new ArrayList<>(); - } - - if (jsonLoginMethods.size() == 0) { - errors.add("At least one loginMethod is required."); - return new ArrayList<>(); - } - - Boolean hasPrimaryLoginMethod = false; - - ArrayList loginMethods = new ArrayList<>(); - for (JsonElement jsonLoginMethod : jsonLoginMethods) { - JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); - - if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { - if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { - if (hasPrimaryLoginMethod) { - errors.add("No two loginMethods can have isPrimary as true."); - } - hasPrimaryLoginMethod = true; - } - } - - loginMethods.add(new LoginMethod(jsonLoginMethodObj)); - } - - return loginMethods; - } - - @SuppressWarnings("unchecked") - private T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, boolean isRequired, - Class targetType, String errorSuffix) { - if (jsonObject.has(key)) { - if (validateJsonFieldType(jsonObject, key, expectedType)) { - T value; - switch (expectedType) { - case STRING: - value = (T) jsonObject.get(key).getAsString(); - break; - case NUMBER: - value = (T) jsonObject.get(key).getAsNumber(); - break; - case BOOLEAN: - Boolean boolValue = jsonObject.get(key).getAsBoolean(); - value = (T) boolValue; - break; - case OBJECT: - value = (T) jsonObject.get(key).getAsJsonObject(); - break; - case ARRAY_OF_OBJECT, ARRAY_OF_STRING: - value = (T) jsonObject.get(key).getAsJsonArray(); - break; - default: - value = null; - break; - } - if (value != null) { - return targetType.cast(value); - } else { - errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); - } - } else { - errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); - } - } else if (isRequired) { - errors.add(key + " is required" + errorSuffix); - } - return null; - } - - public enum ValueType { - STRING, - NUMBER, - BOOLEAN, - OBJECT, - ARRAY_OF_STRING, - ARRAY_OF_OBJECT - } - - private String getTypeForErrorMessage(ValueType type) { - return switch (type) { - case STRING -> "string"; - case NUMBER -> "number"; - case BOOLEAN -> "boolean"; - case OBJECT -> "object"; - case ARRAY_OF_STRING -> "array of string"; - case ARRAY_OF_OBJECT -> "array of object"; - }; - } - - private Boolean validateJsonFieldType(JsonObject jsonObject, String key, ValueType expectedType) { - if (jsonObject.has(key)) { - return switch (expectedType) { - case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString(); - case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); - case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); - case OBJECT -> jsonObject.get(key).isJsonObject(); - case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() - && validateArrayElements(jsonObject.getAsJsonArray(key), expectedType); - default -> false; - }; - } - return false; - } - - private boolean validateArrayElements(JsonArray array, ValueType expectedType) { - List elements = new ArrayList<>(); - array.forEach(elements::add); - - return switch (expectedType) { - case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); - case ARRAY_OF_STRING -> - elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()); - default -> false; - }; - } - - public class TotpDevice { + public static class TotpDevice { public String secretKey; public Number period; public Number skew; public String deviceName; - public TotpDevice(JsonObject jsonTotpDevice) { - this.secretKey = parseAndValidateField(jsonTotpDevice, "secretKey", ValueType.STRING, true, String.class, - " for a totp device."); - this.period = parseAndValidateField(jsonTotpDevice, "period", ValueType.NUMBER, true, Number.class, - " for a totp device."); - this.skew = parseAndValidateField(jsonTotpDevice, "skew", ValueType.NUMBER, true, Number.class, - " for a totp device."); - this.deviceName = parseAndValidateField(jsonTotpDevice, "deviceName", ValueType.STRING, false, String.class, - " for a totp device."); + public TotpDevice(String secretKey, Number period, Number skew, String deviceName) { + this.secretKey = secretKey; + this.period = period; + this.skew = skew; + this.deviceName = deviceName; } } - public class LoginMethod { + public static class LoginMethod { public String tenantId; public Boolean isVerified; public Boolean isPrimary; @@ -236,80 +76,48 @@ public class LoginMethod { public ThirdPartyLoginMethod thirdPartyLoginMethod; public PasswordlessLoginMethod passwordlessLoginMethod; - public LoginMethod(JsonObject jsonLoginMethod) { - this.recipeId = parseAndValidateField(jsonLoginMethod, "recipeId", ValueType.STRING, true, String.class, - " for a loginMethod."); - this.tenantId = parseAndValidateField(jsonLoginMethod, "tenantId", ValueType.STRING, false, String.class, - " for a loginMethod."); - this.isVerified = parseAndValidateField(jsonLoginMethod, "isVerified", ValueType.BOOLEAN, false, - Boolean.class, " for a loginMethod."); - this.isPrimary = parseAndValidateField(jsonLoginMethod, "isPrimary", ValueType.BOOLEAN, false, - Boolean.class, " for a loginMethod."); - Number timeJoined = parseAndValidateField(jsonLoginMethod, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, - false, Number.class, " for a loginMethod"); - this.timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; - - if ("emailpassword".equals(this.recipeId)) { - this.emailPasswordLoginMethod = new EmailPasswordLoginMethod(jsonLoginMethod); - } else if ("thirdparty".equals(this.recipeId)) { - this.thirdPartyLoginMethod = new ThirdPartyLoginMethod(jsonLoginMethod); - } else if ("passwordless".equals(this.recipeId)) { - this.passwordlessLoginMethod = new PasswordlessLoginMethod(jsonLoginMethod); - } else if (this.recipeId != null) { - errors.add( - "Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); - } + public LoginMethod(String tenantId, String recipeId, Boolean isVerified, Boolean isPrimary, long timeJoinedInMSSinceEpoch, EmailPasswordLoginMethod emailPasswordLoginMethod, ThirdPartyLoginMethod thirdPartyLoginMethod, PasswordlessLoginMethod passwordlessLoginMethod) { + this.tenantId = tenantId; + this.recipeId = recipeId; + this.isVerified = isVerified; + this.isPrimary = isPrimary; + this.timeJoinedInMSSinceEpoch = timeJoinedInMSSinceEpoch; + this.emailPasswordLoginMethod = emailPasswordLoginMethod; + this.thirdPartyLoginMethod = thirdPartyLoginMethod; + this.passwordlessLoginMethod = passwordlessLoginMethod; } - public class EmailPasswordLoginMethod { + public static class EmailPasswordLoginMethod { public String email; public String passwordHash; public String hashingAlgorithm; - public EmailPasswordLoginMethod(JsonObject jsonLoginMethod) { - this.email = parseAndValidateField(jsonLoginMethod, "email", ValueType.STRING, true, String.class, - " for an emailpassword recipe."); - this.passwordHash = parseAndValidateField(jsonLoginMethod, "passwordHash", ValueType.STRING, true, - String.class, " for an emailpassword recipe."); - this.hashingAlgorithm = parseAndValidateField(jsonLoginMethod, "hashingAlgorithm", ValueType.STRING, - true, String.class, " for an emailpassword recipe."); - - if (this.hashingAlgorithm != null && !Arrays.asList("bcrypt", "argon2", "firebase_scrypt").contains(hashingAlgorithm)) { - errors.add( - "Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); - } + public EmailPasswordLoginMethod(String email, String passwordHash, String hashingAlgorithm) { + this.email = email; + this.passwordHash = passwordHash; + this.hashingAlgorithm = hashingAlgorithm; } } - public class ThirdPartyLoginMethod { + public static class ThirdPartyLoginMethod { public String email; public String thirdPartyId; public String thirdPartyUserId; - public ThirdPartyLoginMethod(JsonObject jsonObject) { - this.email = parseAndValidateField(jsonObject, "email", ValueType.STRING, true, String.class, - " for a thirdparty recipe."); - this.thirdPartyId = parseAndValidateField(jsonObject, "thirdPartyId", ValueType.STRING, true, - String.class, " for a thirdparty recipe."); - this.thirdPartyUserId = parseAndValidateField(jsonObject, "thirdPartyUserId", ValueType.STRING, true, - String.class, " for a thirdparty recipe."); + public ThirdPartyLoginMethod(String email, String thirdPartyId, String thirdPartyUserId) { + this.email = email; + this.thirdPartyId = thirdPartyId; + this.thirdPartyUserId = thirdPartyUserId; } } - public class PasswordlessLoginMethod { + public static class PasswordlessLoginMethod { public String email; public String phoneNumber; - public PasswordlessLoginMethod(JsonObject jsonObject) { - this.email = parseAndValidateField(jsonObject, "email", ValueType.STRING, false, String.class, - " for a passwordless recipe."); - this.phoneNumber = parseAndValidateField(jsonObject, "phoneNumber", ValueType.STRING, false, - String.class, " for a passwordless recipe."); - - if ((email != null && email.isEmpty()) && (phoneNumber != null && phoneNumber.isEmpty())) { - errors.add( - "Either email or phoneNumber is required for a passwordless recipe."); - } + public PasswordlessLoginMethod(String email, String phoneNumber) { + this.email = email; + this.phoneNumber = phoneNumber; } } } diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java deleted file mode 100644 index 86eed1fa..00000000 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/InvalidBulkImportDataException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.pluginInterface.bulkimport.exceptions; - -import java.util.ArrayList; - -public class InvalidBulkImportDataException extends Exception { - private static final long serialVersionUID = 1L; - public ArrayList errors; - - public InvalidBulkImportDataException(ArrayList errors) { - super("Data has missing or invalid fields. Please check the errors field for more details."); - this.errors = errors; - } - - public void addError(String error) { - this.errors.add(error); - } -} - - diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java index 8285312b..8f05ca8c 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantIdentifierWithStorage.java @@ -19,10 +19,8 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage; import io.supertokens.pluginInterface.session.SessionStorage; import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; diff --git a/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java new file mode 100644 index 00000000..df33d151 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java @@ -0,0 +1,116 @@ +/* + * 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.pluginInterface.utils; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class JsonValidatorUtils { + @SuppressWarnings("unchecked") + public static T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, + boolean isRequired, Class targetType, List errors, String errorSuffix) { + if (jsonObject.has(key)) { + if (validateJsonFieldType(jsonObject, key, expectedType)) { + T value; + switch (expectedType) { + case STRING: + value = (T) jsonObject.get(key).getAsString(); + break; + case NUMBER: + value = (T) jsonObject.get(key).getAsNumber(); + break; + case BOOLEAN: + Boolean boolValue = jsonObject.get(key).getAsBoolean(); + value = (T) boolValue; + break; + case OBJECT: + value = (T) jsonObject.get(key).getAsJsonObject(); + break; + case ARRAY_OF_OBJECT, ARRAY_OF_STRING: + value = (T) jsonObject.get(key).getAsJsonArray(); + break; + default: + value = null; + break; + } + if (value != null) { + return targetType.cast(value); + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else if (isRequired) { + errors.add(key + " is required" + errorSuffix); + } + return null; + } + + public enum ValueType { + STRING, + NUMBER, + BOOLEAN, + OBJECT, + ARRAY_OF_STRING, + ARRAY_OF_OBJECT + } + + private static String getTypeForErrorMessage(ValueType type) { + return switch (type) { + case STRING -> "string"; + case NUMBER -> "number"; + case BOOLEAN -> "boolean"; + case OBJECT -> "object"; + case ARRAY_OF_STRING -> "array of string"; + case ARRAY_OF_OBJECT -> "array of object"; + }; + } + + public static Boolean validateJsonFieldType(JsonObject jsonObject, String key, ValueType expectedType) { + if (jsonObject.has(key)) { + return switch (expectedType) { + case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() + && !jsonObject.get(key).getAsString().isEmpty(); + case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); + case OBJECT -> jsonObject.get(key).isJsonObject(); + case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() + && validateArrayElements(jsonObject.getAsJsonArray(key), expectedType); + default -> false; + }; + } + return false; + } + + public static Boolean validateArrayElements(JsonArray array, ValueType expectedType) { + List elements = new ArrayList<>(); + array.forEach(elements::add); + + return switch (expectedType) { + case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); + case ARRAY_OF_STRING -> + elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() + && !el.getAsString().isEmpty()); + default -> false; + }; + } +} From c764f87a1eef5c3c9018aeb80354663004bc3408 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 20 Feb 2024 12:05:26 +0530 Subject: [PATCH 04/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 21 ++++++++- .../bulkimport/BulkImportUserInfo.java | 47 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index 044acb67..ee0a7e43 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -1,3 +1,19 @@ +/* + * 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.pluginInterface.bulkimport; import java.util.List; @@ -5,10 +21,10 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.gson.JsonObject; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; public interface BulkImportStorage extends NonAuthRecipeStorage { @@ -17,12 +33,13 @@ public interface BulkImportStorage extends NonAuthRecipeStorage { */ void addBulkImportUsers(AppIdentifier appIdentifier, List users) throws StorageQueryException, + TenantOrAppNotFoundException, io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException; /** * Get users from the bulk_import_users table */ - List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, + List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, @Nullable String bulkImportUserId) throws StorageQueryException; /** diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java new file mode 100644 index 00000000..5d91d0f8 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java @@ -0,0 +1,47 @@ +/* + * 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.pluginInterface.bulkimport; + +import com.google.gson.JsonObject; + +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; + +public class BulkImportUserInfo { + public String id; + public String rawData; + public BulkImportUserStatus status; + public Long createdAt; + public Long updatedAt; + + public BulkImportUserInfo(String id, String rawData, BulkImportUserStatus status, Long createdAt, Long updatedAt) { + this.id = id; + this.rawData = rawData; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public JsonObject toJsonObject() { + JsonObject result = new JsonObject(); + result.addProperty("id", id); + result.addProperty("rawData", rawData); + result.addProperty("status", status.toString()); + result.addProperty("createdAt", createdAt); + result.addProperty("updatedAt", updatedAt); + return result; + } +} From 54d9671a777cc55d0c4bea7e2edae9362cea70a9 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 20 Feb 2024 17:50:17 +0530 Subject: [PATCH 05/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 2 +- .../bulkimport/BulkImportUser.java | 6 +- .../utils/JsonValidatorUtils.java | 116 ------------------ 3 files changed, 4 insertions(+), 120 deletions(-) delete mode 100644 src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index ee0a7e43..9874712d 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -40,7 +40,7 @@ void addBulkImportUsers(AppIdentifier appIdentifier, List users) * Get users from the bulk_import_users table */ List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, - @Nullable String bulkImportUserId) throws StorageQueryException; + @Nullable String bulkImportUserId, @Nullable Long createdAt) throws StorageQueryException; /** * Delete users by id from the bulk_import_users table diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index 7861b0bb..7b65ee54 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -53,11 +53,11 @@ public String toString() { public static class TotpDevice { public String secretKey; - public Number period; - public Number skew; + public Integer period; + public Integer skew; public String deviceName; - public TotpDevice(String secretKey, Number period, Number skew, String deviceName) { + public TotpDevice(String secretKey, Integer period, Integer skew, String deviceName) { this.secretKey = secretKey; this.period = period; this.skew = skew; diff --git a/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java deleted file mode 100644 index df33d151..00000000 --- a/src/main/java/io/supertokens/pluginInterface/utils/JsonValidatorUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.pluginInterface.utils; - -import java.util.ArrayList; -import java.util.List; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -public class JsonValidatorUtils { - @SuppressWarnings("unchecked") - public static T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, - boolean isRequired, Class targetType, List errors, String errorSuffix) { - if (jsonObject.has(key)) { - if (validateJsonFieldType(jsonObject, key, expectedType)) { - T value; - switch (expectedType) { - case STRING: - value = (T) jsonObject.get(key).getAsString(); - break; - case NUMBER: - value = (T) jsonObject.get(key).getAsNumber(); - break; - case BOOLEAN: - Boolean boolValue = jsonObject.get(key).getAsBoolean(); - value = (T) boolValue; - break; - case OBJECT: - value = (T) jsonObject.get(key).getAsJsonObject(); - break; - case ARRAY_OF_OBJECT, ARRAY_OF_STRING: - value = (T) jsonObject.get(key).getAsJsonArray(); - break; - default: - value = null; - break; - } - if (value != null) { - return targetType.cast(value); - } else { - errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); - } - } else { - errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); - } - } else if (isRequired) { - errors.add(key + " is required" + errorSuffix); - } - return null; - } - - public enum ValueType { - STRING, - NUMBER, - BOOLEAN, - OBJECT, - ARRAY_OF_STRING, - ARRAY_OF_OBJECT - } - - private static String getTypeForErrorMessage(ValueType type) { - return switch (type) { - case STRING -> "string"; - case NUMBER -> "number"; - case BOOLEAN -> "boolean"; - case OBJECT -> "object"; - case ARRAY_OF_STRING -> "array of string"; - case ARRAY_OF_OBJECT -> "array of object"; - }; - } - - public static Boolean validateJsonFieldType(JsonObject jsonObject, String key, ValueType expectedType) { - if (jsonObject.has(key)) { - return switch (expectedType) { - case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() - && !jsonObject.get(key).getAsString().isEmpty(); - case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); - case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); - case OBJECT -> jsonObject.get(key).isJsonObject(); - case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() - && validateArrayElements(jsonObject.getAsJsonArray(key), expectedType); - default -> false; - }; - } - return false; - } - - public static Boolean validateArrayElements(JsonArray array, ValueType expectedType) { - List elements = new ArrayList<>(); - array.forEach(elements::add); - - return switch (expectedType) { - case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); - case ARRAY_OF_STRING -> - elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() - && !el.getAsString().isEmpty()); - default -> false; - }; - } -} From b919311ec6cac63be93b070c7da99a26f29835d7 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 23 Feb 2024 18:43:12 +0530 Subject: [PATCH 06/10] fix: PR changes --- .../bulkimport/BulkImportUser.java | 12 +++---- .../sqlStorage/BulkImportSQLStorage.java | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index 7b65ee54..f3bb6d8d 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -53,11 +53,11 @@ public String toString() { public static class TotpDevice { public String secretKey; - public Integer period; - public Integer skew; + public int period; + public int skew; public String deviceName; - public TotpDevice(String secretKey, Integer period, Integer skew, String deviceName) { + public TotpDevice(String secretKey, int period, int skew, String deviceName) { this.secretKey = secretKey; this.period = period; this.skew = skew; @@ -67,8 +67,8 @@ public TotpDevice(String secretKey, Integer period, Integer skew, String deviceN public static class LoginMethod { public String tenantId; - public Boolean isVerified; - public Boolean isPrimary; + public boolean isVerified; + public boolean isPrimary; public long timeJoinedInMSSinceEpoch; public String recipeId; @@ -76,7 +76,7 @@ public static class LoginMethod { public ThirdPartyLoginMethod thirdPartyLoginMethod; public PasswordlessLoginMethod passwordlessLoginMethod; - public LoginMethod(String tenantId, String recipeId, Boolean isVerified, Boolean isPrimary, long timeJoinedInMSSinceEpoch, EmailPasswordLoginMethod emailPasswordLoginMethod, ThirdPartyLoginMethod thirdPartyLoginMethod, PasswordlessLoginMethod passwordlessLoginMethod) { + public LoginMethod(String tenantId, String recipeId, boolean isVerified, boolean isPrimary, long timeJoinedInMSSinceEpoch, EmailPasswordLoginMethod emailPasswordLoginMethod, ThirdPartyLoginMethod thirdPartyLoginMethod, PasswordlessLoginMethod passwordlessLoginMethod) { this.tenantId = tenantId; this.recipeId = recipeId; this.isVerified = isVerified; diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java new file mode 100644 index 00000000..eb297619 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java @@ -0,0 +1,34 @@ +/* + * 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.pluginInterface.bulkimport.sqlStorage; + +import javax.annotation.Nonnull; + +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; + +public interface BulkImportSQLStorage extends BulkImportStorage, SQLStorage { + + /** + * Update the status of the users in the bulk_import_users table + */ + void updateBulkImportUserStatus_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, @Nonnull String[] bulkImportUserIds, @Nonnull BulkImportUserStatus status) throws StorageQueryException; +} From f24b8904511023c9021541d6cfc318cf27c6f200 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 10:17:53 +0530 Subject: [PATCH 07/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 2 +- .../bulkimport/BulkImportUser.java | 101 +++++++++--------- .../bulkimport/BulkImportUserInfo.java | 47 -------- 3 files changed, 49 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index 9874712d..27d75d04 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -39,7 +39,7 @@ void addBulkImportUsers(AppIdentifier appIdentifier, List users) /** * Get users from the bulk_import_users table */ - List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, + List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, @Nullable String bulkImportUserId, @Nullable Long createdAt) throws StorageQueryException; /** diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index f3bb6d8d..0f097384 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -21,6 +21,8 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; + public class BulkImportUser { public String id; public String externalUserId; @@ -29,7 +31,13 @@ public class BulkImportUser { public List totpDevices; public List loginMethods; - public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, List userRoles, List totpDevices, List loginMethods) { + // Following fields come from the DB Record. + public BulkImportUserStatus status; + public Long createdAt; + public Long updatedAt; + + public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, List userRoles, + List totpDevices, List loginMethods) { this.id = id; this.externalUserId = externalUserId; this.userMetadata = userMetadata; @@ -38,17 +46,31 @@ public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, this.loginMethods = loginMethods; } - public String toString() { - Gson gson = new Gson(); - JsonObject json = new JsonObject(); + public static BulkImportUser fromDBJson(String id, String rawData, BulkImportUserStatus status, long createdAt, + long updatedAt) { + BulkImportUser user = new Gson().fromJson(rawData, BulkImportUser.class); + user.id = id; + user.status = status; + user.createdAt = createdAt; + user.updatedAt = updatedAt; + return user; + } - json.addProperty("externalUserId", externalUserId); - json.add("userMetadata", userMetadata); - json.add("roles", gson.toJsonTree(userRoles)); - json.add("totp", gson.toJsonTree(totpDevices)); - json.add("loginMethods", gson.toJsonTree(loginMethods)); + public String toString() { + return new Gson().toJson(this); + } - return json.toString(); + // This method returns a JSON object string representation, excluding 'status', 'createdAt', and 'updatedAt'. Useful for test comparisons. + public String toRawData() { + JsonObject jsonObject = new Gson().fromJson(this.toString(), JsonObject.class); + jsonObject.remove("status"); + jsonObject.remove("createdAt"); + jsonObject.remove("updatedAt"); + return jsonObject.toString(); + } + + public JsonObject toJsonObject() { + return new Gson().fromJson(this.toString(), JsonObject.class); } public static class TotpDevice { @@ -71,54 +93,27 @@ public static class LoginMethod { public boolean isPrimary; public long timeJoinedInMSSinceEpoch; public String recipeId; - - public EmailPasswordLoginMethod emailPasswordLoginMethod; - public ThirdPartyLoginMethod thirdPartyLoginMethod; - public PasswordlessLoginMethod passwordlessLoginMethod; - - public LoginMethod(String tenantId, String recipeId, boolean isVerified, boolean isPrimary, long timeJoinedInMSSinceEpoch, EmailPasswordLoginMethod emailPasswordLoginMethod, ThirdPartyLoginMethod thirdPartyLoginMethod, PasswordlessLoginMethod passwordlessLoginMethod) { + public String email; + public String passwordHash; + public String hashingAlgorithm; + public String thirdPartyId; + public String thirdPartyUserId; + public String phoneNumber; + + public LoginMethod(String tenantId, String recipeId, boolean isVerified, boolean isPrimary, + long timeJoinedInMSSinceEpoch, String email, String passwordHash, String hashingAlgorithm, + String thirdPartyId, String thirdPartyUserId, String phoneNumber) { this.tenantId = tenantId; this.recipeId = recipeId; this.isVerified = isVerified; this.isPrimary = isPrimary; this.timeJoinedInMSSinceEpoch = timeJoinedInMSSinceEpoch; - this.emailPasswordLoginMethod = emailPasswordLoginMethod; - this.thirdPartyLoginMethod = thirdPartyLoginMethod; - this.passwordlessLoginMethod = passwordlessLoginMethod; - } - - public static class EmailPasswordLoginMethod { - public String email; - public String passwordHash; - public String hashingAlgorithm; - - public EmailPasswordLoginMethod(String email, String passwordHash, String hashingAlgorithm) { - this.email = email; - this.passwordHash = passwordHash; - this.hashingAlgorithm = hashingAlgorithm; - } - } - - public static class ThirdPartyLoginMethod { - public String email; - public String thirdPartyId; - public String thirdPartyUserId; - - public ThirdPartyLoginMethod(String email, String thirdPartyId, String thirdPartyUserId) { - this.email = email; - this.thirdPartyId = thirdPartyId; - this.thirdPartyUserId = thirdPartyUserId; - } - } - - public static class PasswordlessLoginMethod { - public String email; - public String phoneNumber; - - public PasswordlessLoginMethod(String email, String phoneNumber) { - this.email = email; - this.phoneNumber = phoneNumber; - } + this.email = email; + this.passwordHash = passwordHash; + this.hashingAlgorithm = hashingAlgorithm; + this.thirdPartyId = thirdPartyId; + this.thirdPartyUserId = thirdPartyUserId; + this.phoneNumber = phoneNumber; } } } diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java deleted file mode 100644 index 5d91d0f8..00000000 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUserInfo.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.pluginInterface.bulkimport; - -import com.google.gson.JsonObject; - -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; - -public class BulkImportUserInfo { - public String id; - public String rawData; - public BulkImportUserStatus status; - public Long createdAt; - public Long updatedAt; - - public BulkImportUserInfo(String id, String rawData, BulkImportUserStatus status, Long createdAt, Long updatedAt) { - this.id = id; - this.rawData = rawData; - this.status = status; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public JsonObject toJsonObject() { - JsonObject result = new JsonObject(); - result.addProperty("id", id); - result.addProperty("rawData", rawData); - result.addProperty("status", status.toString()); - result.addProperty("createdAt", createdAt); - result.addProperty("updatedAt", updatedAt); - return result; - } -} From d81681b59ea145908d86e68355cf6219f411ba88 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 10:53:27 +0530 Subject: [PATCH 08/10] fix: PR changes --- .../pluginInterface/bulkimport/BulkImportUser.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index 0f097384..35ce5a05 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -46,14 +46,8 @@ public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, this.loginMethods = loginMethods; } - public static BulkImportUser fromDBJson(String id, String rawData, BulkImportUserStatus status, long createdAt, - long updatedAt) { - BulkImportUser user = new Gson().fromJson(rawData, BulkImportUser.class); - user.id = id; - user.status = status; - user.createdAt = createdAt; - user.updatedAt = updatedAt; - return user; + public static BulkImportUser fromJson(JsonObject jsonObject) { + return new Gson().fromJson(jsonObject, BulkImportUser.class); } public String toString() { From 29345b199da99ffe0a5cb0cb77a9ffd0ff8b461a Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 12:34:31 +0530 Subject: [PATCH 09/10] fix: PR changes --- .../bulkimport/BulkImportStorage.java | 4 +-- .../bulkimport/BulkImportUser.java | 26 ++++++++++++------- .../sqlStorage/BulkImportSQLStorage.java | 2 +- .../AppIdentifierWithStorage.java | 6 ++--- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java index 27d75d04..9c9efd34 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -39,7 +39,7 @@ void addBulkImportUsers(AppIdentifier appIdentifier, List users) /** * Get users from the bulk_import_users table */ - List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, + List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BULK_IMPORT_USER_STATUS status, @Nullable String bulkImportUserId, @Nullable Long createdAt) throws StorageQueryException; /** @@ -49,7 +49,7 @@ List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull In // ArrayList bulkImportUserIds) // throws StorageQueryException; - public enum BulkImportUserStatus { + public enum BULK_IMPORT_USER_STATUS { NEW, PROCESSING, FAILED } } diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index 35ce5a05..ce98f24d 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -21,7 +21,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; public class BulkImportUser { public String id; @@ -32,7 +32,7 @@ public class BulkImportUser { public List loginMethods; // Following fields come from the DB Record. - public BulkImportUserStatus status; + public BULK_IMPORT_USER_STATUS status; public Long createdAt; public Long updatedAt; @@ -50,21 +50,27 @@ public static BulkImportUser fromJson(JsonObject jsonObject) { return new Gson().fromJson(jsonObject, BulkImportUser.class); } - public String toString() { - return new Gson().toJson(this); - } - - // This method returns a JSON object string representation, excluding 'status', 'createdAt', and 'updatedAt'. Useful for test comparisons. - public String toRawData() { - JsonObject jsonObject = new Gson().fromJson(this.toString(), JsonObject.class); + // This method returns a JSON object string representation, excluding 'status', 'createdAt', and 'updatedAt'. + // It is used for inserting the user into the database or during testing. + public String toRawDataForDbStorage() { + JsonObject jsonObject = new Gson().fromJson(new Gson().toJson(this), JsonObject.class); jsonObject.remove("status"); jsonObject.remove("createdAt"); jsonObject.remove("updatedAt"); return jsonObject.toString(); } + + public static BulkImportUser fromRawDataFromDbStorage(String id, String rawData, BULK_IMPORT_USER_STATUS status, long createdAt, long updatedAt) { + BulkImportUser user = new Gson().fromJson(rawData, BulkImportUser.class); + user.id = id; + user.status = status; + user.createdAt = createdAt; + user.updatedAt = updatedAt; + return user; + } public JsonObject toJsonObject() { - return new Gson().fromJson(this.toString(), JsonObject.class); + return new Gson().fromJson(new Gson().toJson(this), JsonObject.class); } public static class TotpDevice { diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java index eb297619..be446472 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java @@ -30,5 +30,5 @@ public interface BulkImportSQLStorage extends BulkImportStorage, SQLStorage { * Update the status of the users in the bulk_import_users table */ void updateBulkImportUserStatus_Transaction(AppIdentifier appIdentifier, - TransactionConnection con, @Nonnull String[] bulkImportUserIds, @Nonnull BulkImportUserStatus status) throws StorageQueryException; + TransactionConnection con, @Nonnull String[] bulkImportUserIds, @Nonnull BULK_IMPORT_USER_STATUS status) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java index 81f61394..ed5db6e3 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/AppIdentifierWithStorage.java @@ -20,7 +20,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; @@ -161,11 +161,11 @@ public ActiveUsersSQLStorage getActiveUsersStorage() { return (ActiveUsersSQLStorage) this.storage; } - public BulkImportStorage getBulkImportStorage() { + public BulkImportSQLStorage getBulkImportStorage() { if (this.storage.getType() != STORAGE_TYPE.SQL) { // we only support SQL for now throw new UnsupportedOperationException(""); } - return (BulkImportStorage) this.storage; + return (BulkImportSQLStorage) this.storage; } } From 0e862f7a13efbdc6d0e5f3e31563d63406c483b2 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 13:06:50 +0530 Subject: [PATCH 10/10] fix: PR changes --- .../supertokens/pluginInterface/bulkimport/BulkImportUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java index ce98f24d..d0d99a1b 100644 --- a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -46,7 +46,7 @@ public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, this.loginMethods = loginMethods; } - public static BulkImportUser fromJson(JsonObject jsonObject) { + public static BulkImportUser fromTesting_fromJson(JsonObject jsonObject) { return new Gson().fromJson(jsonObject, BulkImportUser.class); }