1
0
mirror of https://github.com/google/nomulus synced 2026-07-07 16:46:56 +00:00

Compare commits

...

16 Commits

Author SHA1 Message Date
gbrodman fd7820759d Use token's renewalPrice if renewalBehavior is SPECIFIED (#2502)
Previous PRs and token changes (see b/332928676) have made it so that
SPECIFIED renewalPriceBehavior tokens must have a renewal price. As
such, we can now use that renewalPrice when creating domains with
SPECIFIED tokens.
2024-08-15 19:06:32 +00:00
sarahcaseybot 69359bb1e6 Add QPS and incomplete connections metrics to load test client (#2487)
* Add QPS and incomplete connections metrics to load test client

* Add a failed request count

* Add todos

* Reuse contact

* Add bugs to todos

* small fix

* Clarify QPS
2024-08-14 18:14:17 +00:00
gbrodman 35b602a76e Remove User ID field from SQL (#2523)
This will fail tests until the corresponding PR in Java is deployed.
2024-08-14 17:51:15 +00:00
dependabot[bot] 82002d1f75 Bump axios in /console-webapp in the npm_and_yarn group (#2532)
Bumps the npm_and_yarn group in /console-webapp with 1 update: [axios](https://github.com/axios/axios).


Updates `axios` from 1.7.2 to 1.7.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.2...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 15:44:14 +00:00
Lai Jiang 2fd9b062df Make Nomulus work on GKE with external load balancer (#2527)
This will create a multi-cluster external load balancer exposing HTTP
traffic to nomulus running in clusters in the fleet.
2024-08-14 14:32:39 +00:00
Ben McIlwain ec3804e87e Make domain update flow handle null auth data (#2530)
It's valid for the auth data to be null (although it only happens 10 times
across our entire registry), so the domain update flow should not fail out with
a NullPointerException when the existing state of the data is null and the
update isn't adding that data either.

BUG=http://b/359264787
2024-08-13 18:19:44 +00:00
Pavlo Tkach d0d28cc7e6 Fix console contact delete button not working (#2528) 2024-08-09 16:42:39 +00:00
Pavlo Tkach 2d1260c01b Allow updating icannReferralEmail through the new console ui (#2525) 2024-08-07 16:28:08 +00:00
Pavlo Tkach 06da6a2cc6 Make ContactActionTest deterministic for stop fail under new Hibernate (#2524) 2024-08-07 13:37:13 +00:00
Lai Jiang 858a22f82e Delete a duplicate resource file (#2522)
It already exists under the resources folder.
2024-08-06 18:42:29 +00:00
gbrodman 3c126ddfd4 Remove ID field from User in Java classes and remove UserDao (#2517)
This is the first step in the field removal (second will be removing the
column from SQL once this is deployed).

There's no point in using a UserDao versus just doing the standard
loading-from-DB that we do everywhere else. No need to special-case it.
2024-08-05 20:36:17 +00:00
gbrodman 2b98e6f177 Add deprecation message to old console (#2516) 2024-08-02 15:59:08 +00:00
gbrodman 20036b6a74 Fix wording on registry lock verification (#2518) 2024-08-01 20:17:46 +00:00
Lai Jiang 396cbd6bd3 Remove login_email_address from RegistrarPoc (part 2) (#2510)
Remove the field from the schema.
2024-08-01 17:07:03 +00:00
Lai Jiang 71ea16ff69 Call Workspace Groups API directly from nomulus tool (#2515)
When creating/deleting users, we need to add/remove the emails in
question to/from the console email group (if it exists). This used to be
done synchronously by calling the Groups API directly from the nomulus
tool. However #2488 made it so that in all cases where group membership
is modified, a Cloud Tasks task is created to execute the change on
the server side asynchronously (because there are multiple places where
this change needs to be done, and it is easier to make it all happen on the
server side).

Alas, as it turns out, Cloud Tasks tasks need to be created with a
service account's credential (which is trivially done on the server side
because the ADC is a service account). Nomulus command runs with a user
credential, and we need to grant the relevant user permission to
masquerade as a service account, in order to enqueue tasks from the
nomulus tool. It is therefore easier to just revert to the old behavior.
2024-08-01 15:29:57 +00:00
Pavlo Tkach 45331be166 Add redirect to the new console from the old console for tech support (#2514) 2024-07-31 17:16:12 +00:00
108 changed files with 3449 additions and 3292 deletions
+3 -3
View File
@@ -6451,9 +6451,9 @@
}
},
"node_modules/axios": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -18,7 +18,11 @@
<mat-icon>edit</mat-icon>
Edit
</button>
<button mat-icon-button aria-label="Delete Contact">
<button
mat-icon-button
aria-label="Delete Contact"
(click)="deleteContact()"
>
<mat-icon>delete</mat-icon>
</button>
}
@@ -50,6 +50,9 @@ export class ContactDetailsComponent {
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
complete: () => {
this.goBack();
},
});
}
}
@@ -132,6 +132,18 @@
/>
</mat-form-field>
@if((userDataService.userData()?.globalRole || 'NONE') !== "NONE") {
<mat-form-field appearance="outline">
<mat-label>ICANN Referral Email: </mat-label>
<input
matInput
type="text"
[(ngModel)]="registrarInEdit.icannReferralEmail"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
}
<button mat-flat-button color="primary" type="submit">Save</button>
</form>
</div>
@@ -19,6 +19,7 @@ import {
Registrar,
RegistrarService,
} from 'src/app/registrar/registrar.service';
import { UserDataService } from 'src/app/shared/services/userData.service';
import { WhoisService } from './whois.service';
@Component({
@@ -30,6 +31,7 @@ export default class WhoisEditComponent {
registrarInEdit: Registrar | undefined;
constructor(
public userDataService: UserDataService,
public whoisService: WhoisService,
public registrarService: RegistrarService,
private _snackBar: MatSnackBar
@@ -77,6 +77,7 @@ public class CloudTasksUtils implements Serializable {
@Config("projectId") String projectId,
@Config("locationId") String locationId,
@Config("oauthClientId") String oauthClientId,
// Note that this has to be a service account, due to limitations of the Cloud Tasks API.
@ApplicationDefaultCredential GoogleCredentialsBundle credential,
SerializableCloudTasksClient client) {
this.retrier = retrier;
@@ -1,17 +0,0 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
</domain:delete>
</delete>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Non-renewing domain has reached expiration date.</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -365,7 +365,7 @@ public final class DomainCreateFlow implements MutatingFlow {
createAutorenewBillingEvent(
domainHistoryId,
registrationExpirationTime,
getRenewalPriceInfo(isAnchorTenant, allocationToken, feesAndCredits));
getRenewalPriceInfo(isAnchorTenant, allocationToken));
PollMessage.Autorenew autorenewPollMessage =
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
@@ -688,9 +688,7 @@ public final class DomainCreateFlow implements MutatingFlow {
* AllocationToken} is 'SPECIFIED'.
*/
static RenewalPriceInfo getRenewalPriceInfo(
boolean isAnchorTenant,
Optional<AllocationToken> allocationToken,
FeesAndCredits feesAndCredits) {
boolean isAnchorTenant, Optional<AllocationToken> allocationToken) {
if (isAnchorTenant) {
allocationToken.ifPresent(
token ->
@@ -701,7 +699,7 @@ public final class DomainCreateFlow implements MutatingFlow {
} else if (allocationToken.isPresent()
&& allocationToken.get().getRenewalPriceBehavior() == RenewalPriceBehavior.SPECIFIED) {
return RenewalPriceInfo.create(
RenewalPriceBehavior.SPECIFIED, feesAndCredits.getCreateCost());
RenewalPriceBehavior.SPECIFIED, allocationToken.get().getRenewalPrice().get());
} else {
return RenewalPriceInfo.create(RenewalPriceBehavior.DEFAULT, null);
}
@@ -14,7 +14,6 @@
package google.registry.flows.domain;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.common.collect.Sets.symmetricDifference;
@@ -282,7 +281,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
.removeContacts(remove.getContacts())
.addContacts(add.getContacts())
.setRegistrant(determineUpdatedRegistrant(change, domain))
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
.setAuthInfo(Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
if (!add.getNameservers().isEmpty()) {
domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
@@ -32,7 +32,6 @@ import com.google.common.collect.Streams;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar;
@@ -48,9 +47,7 @@ import google.registry.util.CidrAddressBlock;
import google.registry.util.RegistryEnvironment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
@@ -271,29 +268,21 @@ public final class OteAccountBuilder {
Optional<String> groupEmailAddress, CloudTasksUtils cloudTasksUtils, IamClient iamClient) {
for (User user : users) {
User.grantIapPermission(
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, iamClient);
user.getEmailAddress(), groupEmailAddress, cloudTasksUtils, null, iamClient);
}
}
/** Saves all the OT&amp;E entities we created. */
private void saveAllEntities() {
ImmutableList<Tld> registries = ImmutableList.of(sunriseTld, gaTld, eapTld);
Map<String, User> existingUsers = new HashMap<>();
users.forEach(
user ->
UserDao.loadUser(user.getEmailAddress())
.ifPresent(
existingUser ->
existingUsers.put(existingUser.getEmailAddress(), existingUser)));
if (!replaceExisting) {
checkState(existingUsers.isEmpty(), "Found existing users: %s", existingUsers);
}
tm().transact(
() -> {
if (!replaceExisting) {
ImmutableMap<String, User> existingUsers =
tm().loadByEntitiesIfPresent(users).stream()
.collect(toImmutableMap(User::getEmailAddress, u -> u));
checkState(existingUsers.isEmpty(), "Found existing users: %s", existingUsers);
ImmutableList<VKey<? extends ImmutableObject>> keys =
Streams.concat(
registries.stream().map(tld -> Tld.createVKey(tld.getTldStr())),
@@ -317,16 +306,7 @@ public final class OteAccountBuilder {
tm().putAll(registrars);
});
for (User user : users) {
String email = user.getEmailAddress();
if (existingUsers.containsKey(email)) {
// Note that other roles for the existing user are reset. We do this instead of simply
// saving the new user is that UserDao does not allow us to save the new user with the same
// email as the existing user.
user = existingUsers.get(email).asBuilder().setUserRoles(user.getUserRoles()).build();
}
UserDao.saveUser(user);
}
tm().transact(() -> tm().putAll(ImmutableList.copyOf(users)));
}
private Registrar addAllowedTld(Registrar registrar) {
@@ -14,22 +14,25 @@
package google.registry.model.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.tools.server.UpdateUserGroupAction.GROUP_UPDATE_QUEUE;
import com.google.cloud.tasks.v2.Task;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.persistence.VKey;
import google.registry.request.Action.Service;
import google.registry.tools.IamClient;
import google.registry.tools.ServiceConnection;
import google.registry.tools.server.UpdateUserGroupAction;
import google.registry.tools.server.UpdateUserGroupAction.Mode;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Embeddable;
@@ -46,8 +49,6 @@ public class User extends UserBase {
public static final String IAP_SECURED_WEB_APP_USER_ROLE = "roles/iap.httpsResourceAccessor";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@VisibleForTesting public static final AtomicLong ID_GENERATOR_FOR_TESTING = new AtomicLong();
/**
* Grants the user permission to pass IAP.
*
@@ -57,18 +58,30 @@ public class User extends UserBase {
public static void grantIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
@Nullable CloudTasksUtils cloudTasksUtils,
@Nullable ServiceConnection connection,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
checkArgument(
cloudTasksUtils != null || connection != null,
"At least one of cloudTasksUtils or connection must be set");
checkArgument(
cloudTasksUtils == null || connection == null,
"Only one of cloudTasksUtils or connection can be set");
if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Granting IAP role to user %s", emailAddress);
iamClient.addBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
logger.atInfo().log("Adding %s to group %s", emailAddress, groupEmailAddress.get());
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, UpdateUserGroupAction.Mode.ADD);
if (cloudTasksUtils != null) {
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, UpdateUserGroupAction.Mode.ADD);
} else {
modifyGroupMembershipSync(
emailAddress, groupEmailAddress.get(), connection, UpdateUserGroupAction.Mode.ADD);
}
}
}
@@ -81,18 +94,29 @@ public class User extends UserBase {
public static void revokeIapPermission(
String emailAddress,
Optional<String> groupEmailAddress,
CloudTasksUtils cloudTasksUtils,
@Nullable CloudTasksUtils cloudTasksUtils,
@Nullable ServiceConnection connection,
IamClient iamClient) {
if (RegistryEnvironment.isInTestServer()) {
return;
}
checkArgument(
cloudTasksUtils != null || connection != null,
"At least one of cloudTasksUtils or connection must be set");
checkArgument(
cloudTasksUtils == null || connection == null,
"Only one of cloudTasksUtils or connection can be set");
if (groupEmailAddress.isEmpty()) {
logger.atInfo().log("Removing IAP role from user %s", emailAddress);
iamClient.removeBinding(emailAddress, IAP_SECURED_WEB_APP_USER_ROLE);
} else {
logger.atInfo().log("Removing %s from group %s", emailAddress, groupEmailAddress.get());
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, Mode.REMOVE);
if (cloudTasksUtils != null) {
modifyGroupMembershipAsync(
emailAddress, groupEmailAddress.get(), cloudTasksUtils, Mode.REMOVE);
} else {
modifyGroupMembershipSync(emailAddress, groupEmailAddress.get(), connection, Mode.REMOVE);
}
}
}
@@ -115,10 +139,23 @@ public class User extends UserBase {
cloudTasksUtils.enqueue(GROUP_UPDATE_QUEUE, task);
}
@Override
@Access(AccessType.PROPERTY)
public Long getId() {
return super.getId();
private static void modifyGroupMembershipSync(
String userEmailAddress, String groupEmailAddress, ServiceConnection connection, Mode mode) {
try {
connection.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
userEmailAddress,
"groupEmailAddress",
groupEmailAddress,
"groupUpdateMode",
mode.name()),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
} catch (IOException e) {
throw new RuntimeException("Cannot send request to server", e);
}
}
@Id
@@ -135,7 +172,7 @@ public class User extends UserBase {
@Override
public VKey<User> createVKey() {
return VKey.create(User.class, getId());
return VKey.create(User.class, getEmailAddress());
}
/** Builder for constructing immutable {@link User} objects. */
@@ -146,20 +183,5 @@ public class User extends UserBase {
public Builder(User user) {
super(user);
}
@Override
public User build() {
// Sets the ID temporarily until we can get rid of the non-null constraint (and the field)
if (getInstance().getId() == null || getInstance().getId().equals(0L)) {
// In tests, we cannot guarantee that the database is fully set up -- so don't use it to
// generate a new long
if (RegistryEnvironment.get() == RegistryEnvironment.UNITTEST) {
getInstance().setId(ID_GENERATOR_FOR_TESTING.getAndIncrement());
} else {
getInstance().setId(tm().reTransact(tm()::allocateId));
}
}
return super.build();
}
}
}
@@ -49,9 +49,6 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
private static final long serialVersionUID = 6936728603828566721L;
/** Autogenerated unique ID of this user. */
@Transient private Long id;
/** Email address of the user in question. */
@Transient String emailAddress;
@@ -71,24 +68,15 @@ public class UserBase extends UpdateAutoTimestampEntity implements Buildable {
/** Randomly generated hash salt. */
String registryLockPasswordSalt;
public Long getId() {
return id;
}
/**
* Sets the user ID.
* Sets the user email address.
*
* <p>This should only be used for restoring the user id of an object being loaded in a PostLoad
* method (effectively, when it is still under construction by Hibernate). In all other cases, the
* object should be regarded as immutable and changes should go through a Builder.
* <p>This should only be used for restoring an object being loaded in a PostLoad method
* (effectively, when it is still under construction by Hibernate). In all other cases, the object
* should be regarded as immutable and changes should go through a Builder.
*
* <p>In addition to this special case use, this method must exist to satisfy Hibernate.
*/
@SuppressWarnings("unused")
void setId(Long id) {
this.id = id;
}
void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
@@ -1,38 +0,0 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// 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 google.registry.model.console;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import java.util.Optional;
/** Data access object for {@link User} objects to simplify saving and retrieval. */
public class UserDao {
/** Retrieves the one user with this email address if it exists. */
public static Optional<User> loadUser(String emailAddress) {
return tm().transact(
() ->
tm().query("FROM User WHERE emailAddress = :emailAddress", User.class)
.setParameter("emailAddress", emailAddress)
.getResultStream()
.findFirst());
}
/** Saves the given user, updating it if it already exists. */
public static void saveUser(User user) {
tm().transact(() -> tm().put(user));
}
}
@@ -38,10 +38,6 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
UserBase user;
// This field exists so that it's populated in the SQL table
@Column(nullable = false, name = "userId")
Long id;
@Column(nullable = false, name = "emailAddress")
String emailAddress;
@@ -51,7 +47,6 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
@PostLoad
void postLoad() {
user.setId(id);
user.setEmailAddress(emailAddress);
}
@@ -83,7 +78,6 @@ public class UserUpdateHistory extends ConsoleUpdateHistory {
public Builder setUser(User user) {
getInstance().user = user;
getInstance().id = user.getId();
getInstance().emailAddress = user.getEmailAddress();
return this;
}
@@ -144,6 +144,7 @@ public class DomainBase extends EppResource
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@AttributeOverride(name = "pw.repoId", column = @Column(name = "auth_info_repo_id")),
})
@Nullable
DomainAuthInfo authInfo;
/** Data used to construct DS records for this domain. */
@@ -620,6 +621,7 @@ public class DomainBase extends EppResource
return getAllContacts(true);
}
@Nullable
public DomainAuthInfo getAuthInfo() {
return authInfo;
}
@@ -16,6 +16,7 @@ package google.registry.model.eppcommon;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import javax.annotation.Nullable;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
@@ -49,10 +50,12 @@ public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializ
public static class PasswordAuth extends ImmutableObject implements UnsafeSerializable {
@XmlValue
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
@Nullable
String value;
@XmlAttribute(name = "roid")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
@Nullable
String repoId;
public String getValue() {
@@ -63,14 +66,14 @@ public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializ
return repoId;
}
public static PasswordAuth create(String value, String repoId) {
public static PasswordAuth create(@Nullable String value, @Nullable String repoId) {
PasswordAuth instance = new PasswordAuth();
instance.value = value;
instance.repoId = repoId;
return instance;
}
public static PasswordAuth create(String value) {
public static PasswordAuth create(@Nullable String value) {
return create(value, null);
}
}
@@ -15,6 +15,7 @@
package google.registry.request.auth;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.auth.oauth2.TokenVerifier;
@@ -23,7 +24,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import google.registry.request.auth.AuthModule.IapOidc;
import google.registry.request.auth.AuthModule.RegularOidc;
import google.registry.request.auth.AuthModule.RegularOidcFallback;
@@ -117,7 +118,8 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
logger.atWarning().log("No email address from the OIDC token:\n%s", token.getPayload());
return AuthResult.NOT_AUTHENTICATED;
}
Optional<User> maybeUser = UserDao.loadUser(email);
Optional<User> maybeUser =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(User.class, email)));
if (maybeUser.isPresent()) {
return AuthResult.createUser(maybeUser.get());
}
@@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.tools.params.KeyValueMapParameter.StringToRegistrarRoleMap;
import java.util.Optional;
@@ -95,8 +94,6 @@ public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
builder.setRegistryLockEmailAddress(registryLockEmailAddress);
}
}
User newUser = builder.build();
UserDao.saveUser(newUser);
tm().put(builder.build());
}
}
@@ -16,24 +16,24 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.console.User.grantIapPermission;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameters;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand {
public class CreateUserCommand extends CreateOrUpdateUserCommand implements CommandWithConnection {
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@@ -41,14 +41,20 @@ public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
checkArgument(UserDao.loadUser(email).isEmpty(), "A user with email %s already exists", email);
checkArgument(
!tm().exists(VKey.create(User.class, email)), "A user with email %s already exists", email);
return null;
}
@Override
protected String execute() throws Exception {
String ret = super.execute();
grantIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
grantIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient);
return ret;
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -14,28 +14,27 @@
package google.registry.tools;
import static com.google.api.client.util.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand {
public class DeleteUserCommand extends ConfirmingCommand implements CommandWithConnection {
private ServiceConnection connection;
@Inject IamClient iamClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject
@Config("gSuiteConsoleUserGroupEmailAddress")
Optional<String> maybeGroupEmailAddress;
@@ -47,7 +46,9 @@ public class DeleteUserCommand extends ConfirmingCommand {
@Override
protected String prompt() {
checkArgumentNotNull(email, "Email must be provided");
checkArgumentPresent(UserDao.loadUser(email), "Email does not correspond to a valid user");
checkArgument(
tm().transact(() -> tm().exists(VKey.create(User.class, email))),
"Email does not correspond to a valid user");
return String.format("Delete user with email %s?", email);
}
@@ -55,11 +56,16 @@ public class DeleteUserCommand extends ConfirmingCommand {
protected String execute() throws Exception {
tm().transact(
() -> {
Optional<User> optionalUser = UserDao.loadUser(email);
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get());
VKey<User> key = VKey.create(User.class, email);
checkArgument(tm().exists(key), "Email no longer corresponds to a valid user");
tm().delete(key);
});
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, iamClient);
User.revokeIapPermission(email, maybeGroupEmailAddress, null, connection, iamClient);
return String.format("Deleted user with email %s", email);
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
}
}
@@ -14,10 +14,12 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.persistence.VKey;
import java.util.List;
/** Command to display one or more users. */
@@ -29,11 +31,14 @@ public class GetUserCommand implements Command {
@Override
public void run() throws Exception {
for (String emailAddress : mainParameters) {
System.out.println(
UserDao.loadUser(emailAddress)
.map(User::toString)
.orElse(String.format("No user with email address %s", emailAddress)));
}
tm().transact(
() -> {
for (String emailAddress : mainParameters) {
System.out.println(
tm().loadByKeyIfPresent(VKey.create(User.class, emailAddress))
.map(User::toString)
.orElse(String.format("No user with email address %s", emailAddress)));
}
});
}
}
@@ -14,20 +14,20 @@
package google.registry.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
import google.registry.persistence.VKey;
/** Updates a user, assuming that the user in question already exists. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class UpdateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
return checkArgumentPresent(UserDao.loadUser(email), "User %s not found", email);
return checkArgumentPresent(
tm().loadByKeyIfPresent(VKey.create(User.class, email)), "User %s not found", email);
}
}
@@ -60,7 +60,7 @@ public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction {
RegistryLock lock =
domainLockUtils.verifyVerificationCode(lockVerificationCode, user.getUserRoles().isAdmin());
RegistryLockAction action =
lock.getLockCompletionTime().isPresent()
lock.getUnlockCompletionTime().isPresent()
? RegistryLockAction.UNLOCKED
: RegistryLockAction.LOCKED;
RegistryLockVerificationResponse lockResponse =
@@ -17,6 +17,7 @@ package google.registry.ui.server.console.settings;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
@@ -30,6 +31,7 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.console.ConsoleApiAction;
import google.registry.ui.server.registrar.ConsoleApiParams;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -65,10 +67,10 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
checkArgument(registrar.isPresent(), "'registrar' parameter is not present");
checkPermission(
user, registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
tm().transact(() -> loadAndModifyRegistrar(registrar.get()));
tm().transact(() -> loadAndModifyRegistrar(registrar.get(), user));
}
private void loadAndModifyRegistrar(Registrar providedRegistrar) {
private void loadAndModifyRegistrar(Registrar providedRegistrar, User user) {
Registrar savedRegistrar;
try {
// reload to make sure the object has all the correct fields
@@ -78,6 +80,15 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
return;
}
// icannReferralEmail can't be updated by partners, only by global users with
// EDIT_REGISTRAR_DETAILS permission
if (!Objects.equals(
savedRegistrar.getIcannReferralEmail(), providedRegistrar.getIcannReferralEmail())
&& !user.getUserRoles().hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
setFailedResponse(
"Icann Referral Email update is not permitted for this user", SC_BAD_REQUEST);
}
Registrar newRegistrar =
savedRegistrar
.asBuilder()
@@ -86,6 +97,8 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
.setLocalizedAddress(providedRegistrar.getLocalizedAddress())
.setPhoneNumber(providedRegistrar.getPhoneNumber())
.setFaxNumber(providedRegistrar.getFaxNumber())
.setIcannReferralEmail(providedRegistrar.getIcannReferralEmail())
.setEmailAddress(providedRegistrar.getEmailAddress())
.build();
tm().put(newRegistrar);
sendExternalUpdatesIfNecessary(
@@ -30,7 +30,6 @@ import google.registry.batch.CloudTasksUtils;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
@@ -260,10 +259,10 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
"Registrar with client ID %s already exists",
registrar.getRegistrarId());
tm().put(registrar);
tm().put(user);
});
UserDao.saveUser(user);
User.grantIapPermission(
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, iamClient);
user.getEmailAddress(), maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
data.put("password", password);
data.put("passcode", phonePasscode);
@@ -14,10 +14,13 @@
package google.registry.ui.server.registrar;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.ADMIN;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_PERMANENT_REDIRECT;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.common.base.Supplier;
@@ -26,6 +29,8 @@ import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.tofu.SoyTofu;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.common.FeatureFlag;
import google.registry.model.console.GlobalRole;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
@@ -35,8 +40,10 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.ui.server.SoyTemplateUtils;
import google.registry.ui.soy.registrar.ConsoleSoyInfo;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
/** Action that serves Registrar Console single HTML page (SPA). */
@@ -100,6 +107,7 @@ public final class ConsoleUiAction extends HtmlAction {
soyMapData.put("announcementsEmail", announcementsEmail);
soyMapData.put("supportPhoneNumber", supportPhoneNumber);
soyMapData.put("technicalDocsUrl", technicalDocsUrl);
if (!enabled) {
response.setStatus(SC_SERVICE_UNAVAILABLE);
response.setPayload(
@@ -111,9 +119,35 @@ public final class ConsoleUiAction extends HtmlAction {
.render());
return;
}
// Set permanent redirect to the new console for tech support
if (isNullOrEmpty(req.getParameter("redirect"))
&& Stream.of(GlobalRole.SUPPORT_LEAD, GlobalRole.SUPPORT_AGENT)
.anyMatch(
globalRole ->
globalRole.equals(authResult.user().get().getUserRoles().getGlobalRole()))) {
response.setStatus(SC_PERMANENT_REDIRECT);
try {
response.sendRedirect("/console");
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ImmutableSetMultimap<String, Role> roleMap = registrarAccessor.getAllRegistrarIdsWithRoles();
soyMapData.put("allClientIds", roleMap.keySet());
soyMapData.put("environment", RegistryEnvironment.get().toString());
boolean newConsole =
tm().transact(
() ->
FeatureFlag.getUncached(FeatureFlag.FeatureName.NEW_CONSOLE)
.map(
flag ->
flag.getStatus(tm().getTransactionTime())
.equals(FeatureFlag.FeatureStatus.ACTIVE))
.orElse(false));
soyMapData.put("includeDeprecationWarning", newConsole);
// We set the initial value to the value that will show if guessClientId throws.
String clientId = "<null>";
try {
@@ -261,7 +261,7 @@ li.kd-menulistitem {
top: 136px;
left: 173px;
bottom: 0;
width: 100%;
width: calc(100% - 173px);
margin: 0;
padding: 25px 0 1em 0;
overflow-y: scroll !important;
@@ -296,3 +296,31 @@ li.kd-menulistitem {
.reg-select {
margin-left: 23px;
}
div#reg-deprecation-warning {
position: fixed;
top: 64px;
width: 100%;
left: 0;
text-align: center;
background-color: #ba2a1a;
font-size: 0.6svw;
}
div#reg-deprecation-warning h1 {
color: #ffffff;
}
div#reg-deprecation-warning a {
color: #add8e6;
}
div#reg-accessing-as-role {
margin-top: 40px;
}
@media screen and (max-width: 1600px) {
div#reg-deprecation-warning {
font-size: 0.85vw;
}
}
@@ -36,6 +36,7 @@
{@param technicalDocsUrl: string}
{@param environment: string}
{@param analyticsConfig: [googleAnalyticsId: string|null]}
{@param includeDeprecationWarning: bool}
{call registry.soy.console.header}
{param app: 'registrar' /}
@@ -45,7 +46,7 @@
{call registry.soy.console.googlebar data="all" /}
<div id="reg-app" lang="en-US">
<div id="reg-appbar" class="{css('kd-appbar')}">
<div class="{css('kd-description')}">
<div id="reg-accessing-as-role" class="{css('kd-description')}">
Accessing <span class="{css('kd-value')}">{$clientId}</span> as{sp}
{if $isOwner}<span class="{css('kd-value')}">Owner</span>{/if}
{if $isAdmin}<span class="{css('kd-value')}">Admin</span>{/if}
@@ -53,6 +54,13 @@
{sp}(Switch registrar: {call .clientIdSelect_ data="all" /})
{/if}
</div>
{if $includeDeprecationWarning}
<div id="reg-deprecation-warning">
<h1>Note: this console is deprecated and will be deactivated in October 2024.
Please use the <a href="/console">new console</a> instead.
</h1>
</div>
{/if}
<div id="reg-app-buttons" class="{css('kd-buttonbar')} {css('left')}"></div>
</div>
{call .navbar_ data="all" /}
@@ -344,8 +344,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.and()
.hasPeriodYears(2);
RenewalPriceInfo renewalPriceInfo =
DomainCreateFlow.getRenewalPriceInfo(
isAnchorTenant, Optional.ofNullable(allocationToken), feesAndCredits);
DomainCreateFlow.getRenewalPriceInfo(isAnchorTenant, Optional.ofNullable(allocationToken));
// There should be one bill for the create and one for the recurrence autorenew event.
BillingEvent createBillingEvent =
new BillingEvent.Builder()
@@ -3189,53 +3188,25 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testGetRenewalPriceInfo_isAnchorTenantWithoutToken_returnsNonPremiumAndNullPrice() {
assertThat(
DomainCreateFlow.getRenewalPriceInfo(
true,
Optional.empty(),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, false))
.build()))
assertThat(DomainCreateFlow.getRenewalPriceInfo(true, Optional.empty()))
.isEqualTo(RenewalPriceInfo.create(NONPREMIUM, null));
}
@Test
void testGetRenewalPriceInfo_isAnchorTenantWithDefaultToken_returnsNonPremiumAndNullPrice() {
assertThat(
DomainCreateFlow.getRenewalPriceInfo(
true,
Optional.of(allocationToken),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, false))
.build()))
assertThat(DomainCreateFlow.getRenewalPriceInfo(true, Optional.of(allocationToken)))
.isEqualTo(RenewalPriceInfo.create(NONPREMIUM, null));
}
@Test
void testGetRenewalPriceInfo_isNotAnchorTenantWithDefaultToken_returnsDefaultAndNullPrice() {
assertThat(
DomainCreateFlow.getRenewalPriceInfo(
false,
Optional.of(allocationToken),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(100), FeeType.CREATE, false))
.build()))
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.of(allocationToken)))
.isEqualTo(RenewalPriceInfo.create(DEFAULT, null));
}
@Test
void testGetRenewalPriceInfo_isNotAnchorTenantWithoutToken_returnsDefaultAndNullPrice() {
assertThat(
DomainCreateFlow.getRenewalPriceInfo(
false,
Optional.empty(),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(100), FeeType.CREATE, false))
.build()))
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.empty()))
.isEqualTo(RenewalPriceInfo.create(DEFAULT, null));
}
@@ -3248,17 +3219,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRenewalPriceBehavior(SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setRenewalPrice(Money.of(USD, 5))
.build());
assertThat(
DomainCreateFlow.getRenewalPriceInfo(
false,
Optional.of(token),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(100), FeeType.CREATE, false))
.build()))
.isEqualTo(RenewalPriceInfo.create(SPECIFIED, Money.of(USD, 100)));
assertThat(DomainCreateFlow.getRenewalPriceInfo(false, Optional.of(token)))
.isEqualTo(RenewalPriceInfo.create(SPECIFIED, Money.of(USD, 5)));
}
@Test
@@ -3276,11 +3240,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setTokenType(SINGLE_USE)
.setRenewalPriceBehavior(SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.build())),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, true))
.build()));
.build()))));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Renewal price behavior cannot be SPECIFIED for anchor tenant");
@@ -3300,11 +3260,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRenewalPriceBehavior(RenewalPriceBehavior.valueOf("INVALID"))
.build())),
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(Fee.create(BigDecimal.valueOf(0), FeeType.CREATE, true))
.build()));
.build()))));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
@@ -104,8 +104,10 @@ import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
@@ -264,6 +266,38 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
doSuccessfulTest("generic_success_response_no_cltrid.xml");
}
@Test
void testSuccess_noExistingAuthData() throws Exception {
setEppInput("domain_update_no_auth_change.xml");
persistReferencedEntities();
persistDomain();
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(null)))
.build());
assertMutatingFlow(true);
runFlowAssertResponse(loadFile("generic_success_response.xml"));
Domain domain = reloadResourceByForeignKey();
// Check that the domain was updated. These values came from the xml.
assertAboutDomains()
.that(domain)
.hasStatusValue(CLIENT_HOLD)
.and()
.hasAuthInfoPwd(null)
.and()
.hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_UPDATE)
.and()
.hasLastEppUpdateTime(clock.nowUtc())
.and()
.hasLastEppUpdateRegistrarId("TheRegistrar")
.and()
.hasNoAutorenewEndTime();
assertNoBillingEvents();
assertDomainDnsRequests("example.tld");
assertLastHistoryContainsResource(reloadResourceByForeignKey());
}
@Test
void testSuccess_cachingDisabled() throws Exception {
boolean origIsCachingEnabled = RegistryConfig.isEppResourceCachingEnabled();
@@ -22,6 +22,7 @@ import static google.registry.persistence.transaction.JpaTransactionManagerExten
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByKeyIfPresent;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistSimpleResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -36,10 +37,10 @@ import com.google.common.collect.ImmutableList;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldState;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.CloudTasksHelper;
@@ -106,7 +107,7 @@ public final class OteAccountBuilderTest {
}
public static void verifyUser(String registrarId, String email) {
Optional<User> maybeUser = UserDao.loadUser(email);
Optional<User> maybeUser = loadByKeyIfPresent(VKey.create(User.class, email));
assertThat(maybeUser).isPresent();
assertThat(maybeUser.get().getUserRoles().getRegistrarRoles().get(registrarId))
.isEqualTo(RegistrarRole.ACCOUNT_MANAGER);
@@ -1,83 +0,0 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// 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 google.registry.model.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import google.registry.model.EntityTestCase;
import org.junit.jupiter.api.Test;
/** Tests for {@link UserDao}. */
public class UserDaoTest extends EntityTestCase {
@Test
void testSuccess_saveAndRetrieve() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
User user2 =
new User.Builder()
.setEmailAddress("foo@bar.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
UserDao.saveUser(user1);
UserDao.saveUser(user2);
assertAboutImmutableObjects()
.that(user1)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "id", "updateTimestamp");
assertAboutImmutableObjects()
.that(user2)
.isEqualExceptFields(UserDao.loadUser("foo@bar.com").get(), "id", "updateTimestamp");
}
@Test
void testSuccess_absentUser() {
User user =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
UserDao.saveUser(user);
User fromDb = UserDao.loadUser("email@email.com").get();
// nonexistent one should never exist
assertThat(UserDao.loadUser("nonexistent@email.com")).isEmpty();
// now try deleting the one that does exist
tm().transact(() -> tm().delete(fromDb));
assertThat(UserDao.loadUser("email@email.com")).isEmpty();
}
@Test
void testSuccess_updateUser_sameEmail() {
User user1 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build())
.build();
User user2 =
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user1);
UserDao.saveUser(user2);
assertAboutImmutableObjects()
.that(user2)
.isEqualExceptFields(UserDao.loadUser("email@email.com").get(), "updateTimestamp");
}
}
@@ -21,15 +21,20 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.model.EntityTestCase;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.IamClient;
import google.registry.tools.ServiceConnection;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.Test;
@@ -57,6 +62,34 @@ public class UserTest extends EntityTestCase {
});
}
@Test
void testFailure_asyncAndSyncModeConflict() {
assertThrows(
IllegalArgumentException.class,
() -> User.grantIapPermission("email@example.com", Optional.empty(), null, null, null));
assertThrows(
IllegalArgumentException.class,
() ->
User.grantIapPermission(
"email@example.com",
Optional.empty(),
mock(CloudTasksUtils.class),
mock(ServiceConnection.class),
null));
assertThrows(
IllegalArgumentException.class,
() -> User.revokeIapPermission("email@example.com", Optional.empty(), null, null, null));
assertThrows(
IllegalArgumentException.class,
() ->
User.revokeIapPermission(
"email@example.com",
Optional.empty(),
mock(CloudTasksUtils.class),
mock(ServiceConnection.class),
null));
}
@Test
void testFailure_badInputs() {
User.Builder builder = new User.Builder();
@@ -116,19 +149,20 @@ public class UserTest extends EntityTestCase {
}
@Test
void testGrantIapPermission() {
void testGrantIapPermissionAsync() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, iamClient);
User.grantIapPermission(
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertNoTasksEnqueued();
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, iamClient);
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
@@ -142,19 +176,49 @@ public class UserTest extends EntityTestCase {
}
@Test
void testRevokeIapPermission() {
void testGrantIapPermissionSync() throws Exception {
ServiceConnection connection = mock(ServiceConnection.class);
IamClient iamClient = mock(IamClient.class);
// Individual permission.
User.grantIapPermission("email@example.com", Optional.empty(), null, connection, iamClient);
verifyNoInteractions(connection);
verify(iamClient).addBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.grantIapPermission(
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient);
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.com",
"groupEmailAddress",
"console@example.com",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoMoreInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
void testRevokeIapPermissionAsync() {
CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
IamClient iamClient = mock(IamClient.class);
CloudTasksUtils cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
// Individual permission.
User.revokeIapPermission("email@example.com", Optional.empty(), cloudTasksUtils, iamClient);
User.revokeIapPermission(
"email@example.com", Optional.empty(), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertNoTasksEnqueued();
verify(iamClient).removeBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.revokeIapPermission(
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, iamClient);
"email@example.com", Optional.of("console@example.com"), cloudTasksUtils, null, iamClient);
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
@@ -166,4 +230,33 @@ public class UserTest extends EntityTestCase {
.param("groupUpdateMode", "REMOVE"));
verifyNoMoreInteractions(iamClient);
}
@Test
void testRevokeIapPermissionSync() throws Exception {
ServiceConnection connection = mock(ServiceConnection.class);
IamClient iamClient = mock(IamClient.class);
// Individual permission.
User.revokeIapPermission("email@example.com", Optional.empty(), null, connection, iamClient);
verifyNoInteractions(connection);
verify(iamClient).removeBinding("email@example.com", IAP_SECURED_WEB_APP_USER_ROLE);
// Group membership.
User.revokeIapPermission(
"email@example.com", Optional.of("console@example.com"), null, connection, iamClient);
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.com",
"groupEmailAddress",
"console@example.com",
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoMoreInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
}
@@ -73,7 +73,6 @@ import google.registry.model.billing.BillingRecurrence;
import google.registry.model.common.DnsRefreshRequest;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAuthInfo;
@@ -1014,20 +1013,33 @@ public final class DatabaseHelper {
return tm().transact(() -> tm().loadByEntity(resource));
}
public static User loadExistingUser(String emailAddress) {
return loadByKey(VKey.create(User.class, emailAddress));
}
/** Persists an admin {@link User} with the given email address if it doesn't already exist. */
public static User createAdminUser(String emailAddress) {
Optional<User> existingUser = UserDao.loadUser(emailAddress);
if (existingUser.isPresent()) {
return existingUser.get();
}
User user =
new User.Builder()
.setEmailAddress(emailAddress)
.setUserRoles(
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
.build();
tm().transact(() -> tm().put(user));
return UserDao.loadUser(emailAddress).get();
// Reload the user to pick up the update time
return loadByEntity(
tm().transact(
() -> {
VKey<User> key = VKey.create(User.class, emailAddress);
Optional<User> existingUser = tm().loadByKeyIfPresent(key);
if (existingUser.isPresent()) {
return existingUser.get();
}
User user =
new User.Builder()
.setEmailAddress(emailAddress)
.setUserRoles(
new UserRoles.Builder()
.setGlobalRole(GlobalRole.FTE)
.setIsAdmin(true)
.build())
.build();
tm().put(user);
return user;
}));
}
/** Returns all the history entries that are parented off the given EppResource. */
@@ -29,6 +29,7 @@ import google.registry.model.eppcommon.AuthInfo;
import google.registry.testing.TruthChainer.And;
import google.registry.tmch.LordnTaskUtils.LordnPhase;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Truth subject for asserting things about {@link Domain} instances. */
@@ -65,7 +66,7 @@ public final class DomainSubject extends AbstractEppResourceSubject<Domain, Doma
return hasValue(lordnPhase, actual.getLordnPhase(), "lordnPhase");
}
public And<DomainSubject> hasAuthInfoPwd(String pw) {
public And<DomainSubject> hasAuthInfoPwd(@Nullable String pw) {
AuthInfo authInfo = actual.getAuthInfo();
return hasValue(pw, authInfo == null ? null : authInfo.getPw().getValue(), "has auth info pw");
}
@@ -16,22 +16,21 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.console.User.IAP_SECURED_WEB_APP_USER_ROLE;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.net.MediaType;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -40,13 +39,13 @@ import org.junit.jupiter.api.Test;
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final ServiceConnection connection = mock(ServiceConnection.class);
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.setConnection(connection);
}
@Test
@@ -59,7 +58,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -71,16 +70,20 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "user@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "ADD"));
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"user@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"ADD"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -90,27 +93,27 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
"user@example.test",
"--registry_lock_email_address",
"registrylockemail@otherexample.test");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress())
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
.hasValue("registrylockemail@otherexample.test");
}
@Test
void testSuccess_admin() throws Exception {
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
void testSuccess_globalRole() throws Exception {
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -120,7 +123,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
@@ -129,7 +132,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
RegistrarRole.PRIMARY_CONTACT));
verify(iamClient).addBinding("user@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -144,7 +147,7 @@ public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
.hasMessageThat()
.isEqualTo("A user with email user@example.test already exists");
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
@@ -22,11 +22,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import google.registry.model.console.UserDao;
import google.registry.testing.CloudTasksHelper;
import google.registry.testing.CloudTasksHelper.TaskMatcher;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import google.registry.model.console.User;
import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import google.registry.tools.server.UpdateUserGroupAction;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,43 +36,49 @@ import org.junit.jupiter.api.Test;
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
private final IamClient iamClient = mock(IamClient.class);
private final CloudTasksHelper cloudTasksHelper = new CloudTasksHelper();
private final ServiceConnection connection = mock(ServiceConnection.class);
@BeforeEach
void beforeEach() {
command.iamClient = iamClient;
command.maybeGroupEmailAddress = Optional.empty();
command.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
command.setConnection(connection);
}
@Test
void testSuccess_deletesUser() throws Exception {
DatabaseHelper.createAdminUser("email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isPresent();
VKey<User> key = VKey.create(User.class, "email@example.test");
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isEmpty();
verify(iamClient).removeBinding("email@example.test", IAP_SECURED_WEB_APP_USER_ROLE);
verifyNoMoreInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
@Test
void testSuccess_deletesUser_removeFromGroup() throws Exception {
command.maybeGroupEmailAddress = Optional.of("group@example.test");
DatabaseHelper.createAdminUser("email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isPresent();
VKey<User> key = VKey.create(User.class, "email@example.test");
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
cloudTasksHelper.assertTasksEnqueued(
"console-user-group-update",
new TaskMatcher()
.method(HttpMethod.POST)
.service("TOOLS")
.path("/_dr/admin/updateUserGroup")
.param("userEmailAddress", "email@example.test")
.param("groupEmailAddress", "group@example.test")
.param("groupUpdateMode", "REMOVE"));
assertThat(DatabaseHelper.loadByKeyIfPresent(key)).isEmpty();
verify(connection)
.sendPostRequest(
UpdateUserGroupAction.PATH,
ImmutableMap.of(
"userEmailAddress",
"email@example.test",
"groupEmailAddress",
"group@example.test",
"groupUpdateMode",
"REMOVE"),
MediaType.PLAIN_TEXT_UTF_8,
new byte[0]);
verifyNoInteractions(iamClient);
verifyNoMoreInteractions(connection);
}
@Test
@@ -83,6 +90,6 @@ public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
.hasMessageThat()
.isEqualTo("Email does not correspond to a valid user");
verifyNoInteractions(iamClient);
cloudTasksHelper.assertNoTasksEnqueued("console-user-group-update");
verifyNoInteractions(connection);
}
}
@@ -14,12 +14,13 @@
package google.registry.tools;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.testing.DatabaseHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,8 +29,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
@BeforeEach
void beforeEach() {
User.ID_GENERATOR_FOR_TESTING.set(0L);
UserDao.saveUser(
DatabaseHelper.putInDb(
new User.Builder()
.setEmailAddress("johndoe@theregistrar.com")
.setUserRoles(
@@ -37,8 +37,7 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
.build())
.build());
UserDao.saveUser(
.build(),
new User.Builder()
.setEmailAddress("fte@google.com")
.setUserRoles(
@@ -53,7 +52,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=fte@google.com
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -76,7 +74,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -91,7 +88,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
}
User: {
emailAddress=fte@google.com
id=1
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -114,7 +110,6 @@ public class GetUserCommandTest extends CommandTestCase<GetUserCommand> {
"""
User: {
emailAddress=johndoe@theregistrar.com
id=0
registryLockEmailAddress=null
registryLockPasswordHash=null
registryLockPasswordSalt=null
@@ -22,9 +22,11 @@ import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.putInDb;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -39,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
@@ -447,12 +448,11 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
@Test
void testFailure_userExists() {
User user =
putInDb(
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
.build());
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
@@ -494,12 +494,11 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
@Test
void testSuccess_userExists_replaceExisting() throws Exception {
User user =
putInDb(
new User.Builder()
.setEmailAddress("contact@email.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
UserDao.saveUser(user);
.build());
runCommandForced(
"--overwrite",
@@ -520,7 +519,7 @@ class SetupOteCommandTest extends CommandTestCase<SetupOteCommand> {
verifyUser("blobio-5", "contact@email.com");
// verify that the role is completely replaced, e.g., the global role is gone.
assertThat(UserDao.loadUser("contact@email.com").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("contact@email.com").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
verifyIapPermission("contact@email.com");
@@ -15,13 +15,14 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.loadExistingUser;
import static google.registry.testing.DatabaseHelper.putInDb;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -31,7 +32,7 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
@BeforeEach
void beforeEach() throws Exception {
UserDao.saveUser(
putInDb(
new User.Builder()
.setEmailAddress("user@example.test")
.setUserRoles(new UserRoles.Builder().build())
@@ -45,41 +46,39 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
"user@example.test",
"--registry_lock_email_address",
"registrylockemail@otherexample.test");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress())
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress())
.hasValue("registrylockemail@otherexample.test");
}
@Test
void testSuccess_removeRegistryLockEmail() throws Exception {
UserDao.saveUser(
UserDao.loadUser("user@example.test")
.get()
putInDb(
loadExistingUser("user@example.test")
.asBuilder()
.setRegistryLockEmailAddress("registrylock@otherexample.test")
.build());
runCommandForced("--email", "user@example.test", "--registry_lock_email_address", "");
assertThat(UserDao.loadUser("user@example.test").get().getRegistryLockEmailAddress()).isEmpty();
assertThat(loadExistingUser("user@example.test").getRegistryLockEmailAddress()).isEmpty();
}
@Test
void testSuccess_admin() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isFalse();
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isTrue();
runCommandForced("--email", "user@example.test", "--admin", "false");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
assertThat(loadExistingUser("user@example.test").getUserRoles().isAdmin()).isFalse();
}
@Test
void testSuccess_registrarRoles() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles()).isEmpty();
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
@@ -87,16 +86,15 @@ public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
runCommandForced("--email", "user@example.test", "--registrar_roles", "");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
assertThat(loadExistingUser("user@example.test").getUserRoles().getRegistrarRoles()).isEmpty();
}
@Test
void testSuccess_globalRole() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
assertThat(loadExistingUser("user@example.test").getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@@ -90,7 +90,7 @@ public class ConsoleRegistryLockVerifyActionTest {
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getPayload())
.isEqualTo(
"{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
"{\"action\":\"locked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
assertThat(loadByEntity(defaultDomain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@@ -123,7 +123,7 @@ public class ConsoleRegistryLockVerifyActionTest {
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getPayload())
.isEqualTo(
"{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
"{\"action\":\"locked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}");
assertThat(loadByEntity(defaultDomain).getStatusValues())
.containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES);
}
@@ -17,7 +17,6 @@ package google.registry.ui.server.console.settings;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.testing.DatabaseHelper.createAdminUser;
import static google.registry.testing.DatabaseHelper.insertInDb;
import static google.registry.testing.DatabaseHelper.loadAllOf;
@@ -67,7 +66,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar1@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.9999999999\",\"faxNumber\":\"+1.9999999991\","
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private static String jsonRegistrar2 =
@@ -75,7 +74,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar2@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.1234567890\",\"faxNumber\":\"+1.1234567891\","
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private Registrar testRegistrar;
@@ -97,7 +96,7 @@ class ContactActionTest {
.setEmailAddress("test.registrar1@example.com")
.setPhoneNumber("+1.9999999999")
.setFaxNumber("+1.9999999991")
.setTypes(ImmutableSet.of(WHOIS, ADMIN))
.setTypes(ImmutableSet.of(ADMIN))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
@@ -218,22 +217,22 @@ class ContactActionTest {
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " REMOVED:\n"
+ " {name=Test Registrar 1, emailAddress=incorrect@email.com,"
+ " registrarId=registrarId, registryLockEmailAddress=null,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[WHOIS,"
+ " ADMIN], visibleInWhoisAsAdmin=true,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true,"
+ " visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " FINAL CONTENTS:\n"
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS],"
+ " faxNumber=+1.9999999991, types=[ADMIN],"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n")
@@ -104,6 +104,8 @@ public class WhoisRegistrarFieldsActionTest {
ImmutableMap.of(
"whoisServer",
"whois.nic.google",
"icannReferralEmail",
"lol@sloth.test",
"phoneNumber",
"+1.4155552671",
"faxNumber",
@@ -29,6 +29,8 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.FeatureFlag;
import google.registry.model.console.RegistrarRole;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
@@ -110,15 +112,6 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("page");
}
/** Admins shouldn't have the "add" button */
@RetryingTest(3)
void settingsContact_asAdmin() throws Throwable {
server.setIsAdmin(true);
driver.get(server.getUrl("/registrar?clientId=NewRegistrar#contact-settings"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
@RetryingTest(3)
void settingsContactItem() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
@@ -518,6 +511,18 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("page");
}
@RetryingTest(3)
void deprecationWarning_active() throws Throwable {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(FeatureFlag.FeatureName.NEW_CONSOLE)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, FeatureFlag.FeatureStatus.ACTIVE))
.build());
driver.get(server.getUrl("/registrar"));
driver.waitForDisplayedElement(By.tagName("h1"));
driver.diffPage("page");
}
private static void createDomainAndSaveLock() {
createTld("tld");
Domain domain = persistActiveDomain("example.tld");
@@ -0,0 +1,26 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>ns2.example.foo</domain:hostObj>
</domain:ns>
<domain:contact type="tech">mak21</domain:contact>
<domain:status s="clientHold"
lang="en">Payment overdue.</domain:status>
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>ns1.example.foo</domain:hostObj>
</domain:ns>
<domain:contact type="tech">sh8013</domain:contact>
<domain:status s="clientUpdateProhibited"/>
</domain:rem>
</domain:update>
</update>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

@@ -261,11 +261,11 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-07-30 18:27:56</td>
<td class="property_value">2024-08-14 16:47:59</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V175__user_update_history_id.sql</td>
<td id="lastFlywayFile" class="property_value">V178__drop_user_id_history.sql</td>
</tr>
</tbody>
</table>
@@ -280,7 +280,7 @@ td.section {
<text text-anchor="start" x="3795" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="3878" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.21.4</text>
<text text-anchor="start" x="3794" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="3878" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-07-30 18:27:56</text>
<text text-anchor="start" x="3878" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-08-14 16:47:59</text>
<polygon fill="none" stroke="#888888" points="3791,-4 3791,-44 4027,-44 4027,-4 3791,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -348,96 +348,96 @@ td.section {
<title>
billingevent_a57d1815:w-&gt;allocationtoken_a08ccbef:e
</title>
<path fill="none" stroke="black" d="M2976.47,-1166.26C2968.82,-1148.77 2990.03,-1107.97 2966,-1088.5 2912.26,-1044.95 2724.52,-1069.92 2656,-1060.5 2586.15,-1050.89 1512.01,-836.04 1376.81,-822.2" />
<path fill="none" stroke="black" d="M2976.47,-1166.26C2968.82,-1148.77 2990.03,-1107.97 2966,-1088.5 2912.26,-1044.95 2724.52,-1069.92 2656,-1060.5 2514.6,-1041.05 1552.85,-830.4 1376.53,-821.77" />
<polygon fill="black" stroke="black" points="2983.61,-1169.93 2990.44,-1178.5 2988.05,-1172.21 2992.5,-1174.5 2992.5,-1174.5 2992.5,-1174.5 2988.05,-1172.21 2994.56,-1170.5 2983.61,-1169.93 2983.61,-1169.93" />
<ellipse fill="none" stroke="black" cx="2980.05" cy="-1168.1" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1367.16,-826.56 1367.84,-816.58 1369.83,-816.71 1369.16,-826.69 1367.16,-826.56" />
<polyline fill="none" stroke="black" points="1366.5,-821.5 1371.49,-821.84 " />
<polygon fill="black" stroke="black" points="1372.15,-826.89 1372.82,-816.92 1374.82,-817.05 1374.14,-827.03 1372.15,-826.89" />
<polyline fill="none" stroke="black" points="1371.49,-821.84 1376.48,-822.18 " />
<text text-anchor="start" x="2094.5" y="-996.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_allocation_token</text>
<polygon fill="black" stroke="black" points="1367.36,-826.53 1367.64,-816.53 1369.63,-816.58 1369.36,-826.58 1367.36,-826.53" />
<polyline fill="none" stroke="black" points="1366.5,-821.5 1371.5,-821.64 " />
<polygon fill="black" stroke="black" points="1372.36,-826.66 1372.63,-816.67 1374.63,-816.72 1374.36,-826.72 1372.36,-826.66" />
<polyline fill="none" stroke="black" points="1371.5,-821.64 1376.5,-821.77 " />
<text text-anchor="start" x="2094.5" y="-997.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_allocation_token</text>
</g> <!-- domainhistory_a54cc226 -->
<g id="node4" class="node">
<title>
domainhistory_a54cc226
</title>
<polygon fill="#e9c2f2" stroke="transparent" points="2337,-878.5 2337,-897.5 2503,-897.5 2503,-878.5 2337,-878.5" />
<text text-anchor="start" x="2339" y="-885.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."DomainHistory"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="2503,-878.5 2503,-897.5 2629,-897.5 2629,-878.5 2503,-878.5" />
<text text-anchor="start" x="2590" y="-884.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="2339" y="-866.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">history_revision_id</text>
<text text-anchor="start" x="2497" y="-865.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-865.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8 not null</text>
<text text-anchor="start" x="2339" y="-846.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_registrar_id</text>
<text text-anchor="start" x="2497" y="-846.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-846.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="2339" y="-827.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_modification_time</text>
<text text-anchor="start" x="2497" y="-827.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-827.3" font-family="Helvetica,sans-Serif" font-size="14.00">timestamptz not null</text>
<text text-anchor="start" x="2339" y="-808.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_type</text>
<text text-anchor="start" x="2497" y="-808.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-808.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="2339" y="-789.3" font-family="Helvetica,sans-Serif" font-size="14.00">creation_time</text>
<text text-anchor="start" x="2497" y="-789.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-789.3" font-family="Helvetica,sans-Serif" font-size="14.00">timestamptz</text>
<text text-anchor="start" x="2339" y="-771.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">domain_repo_id</text>
<text text-anchor="start" x="2497" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-770.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="2339" y="-751.3" font-family="Helvetica,sans-Serif" font-size="14.00">current_package_token</text>
<text text-anchor="start" x="2497" y="-751.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-751.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<polygon fill="none" stroke="#888888" points="2336,-744.5 2336,-898.5 2630,-898.5 2630,-744.5 2336,-744.5" />
<polygon fill="#e9c2f2" stroke="transparent" points="2337,-876.5 2337,-895.5 2503,-895.5 2503,-876.5 2337,-876.5" />
<text text-anchor="start" x="2339" y="-883.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."DomainHistory"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="2503,-876.5 2503,-895.5 2629,-895.5 2629,-876.5 2503,-876.5" />
<text text-anchor="start" x="2590" y="-882.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="2339" y="-864.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">history_revision_id</text>
<text text-anchor="start" x="2497" y="-863.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-863.3" font-family="Helvetica,sans-Serif" font-size="14.00">int8 not null</text>
<text text-anchor="start" x="2339" y="-844.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_registrar_id</text>
<text text-anchor="start" x="2497" y="-844.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-844.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<text text-anchor="start" x="2339" y="-825.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_modification_time</text>
<text text-anchor="start" x="2497" y="-825.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-825.3" font-family="Helvetica,sans-Serif" font-size="14.00">timestamptz not null</text>
<text text-anchor="start" x="2339" y="-806.3" font-family="Helvetica,sans-Serif" font-size="14.00">history_type</text>
<text text-anchor="start" x="2497" y="-806.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-806.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="2339" y="-787.3" font-family="Helvetica,sans-Serif" font-size="14.00">creation_time</text>
<text text-anchor="start" x="2497" y="-787.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-787.3" font-family="Helvetica,sans-Serif" font-size="14.00">timestamptz</text>
<text text-anchor="start" x="2339" y="-769.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">domain_repo_id</text>
<text text-anchor="start" x="2497" y="-768.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-768.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="2339" y="-749.3" font-family="Helvetica,sans-Serif" font-size="14.00">current_package_token</text>
<text text-anchor="start" x="2497" y="-749.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="2505" y="-749.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<polygon fill="none" stroke="#888888" points="2336,-742.5 2336,-896.5 2630,-896.5 2630,-742.5 2336,-742.5" />
</g> <!-- billingevent_a57d1815&#45;&gt;domainhistory_a54cc226 -->
<g id="edge31" class="edge">
<title>
billingevent_a57d1815:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M2976.47,-1205.25C2946.11,-1162.93 3005.94,-970.66 2966,-931.5 2916.68,-883.14 2707.6,-954.43 2656,-908.5 2613.3,-870.49 2681.22,-785.44 2640.12,-774.64" />
<path fill="none" stroke="black" d="M2976.47,-1205.25C2946.11,-1162.93 3005.94,-970.66 2966,-931.5 2916.68,-883.14 2707.54,-954.5 2656,-908.5 2612.65,-869.81 2682.2,-783.25 2640,-772.59" />
<polygon fill="black" stroke="black" points="2983.61,-1208.92 2990.44,-1217.5 2988.05,-1211.21 2992.5,-1213.5 2992.5,-1213.5 2992.5,-1213.5 2988.05,-1211.21 2994.56,-1209.5 2983.61,-1208.92 2983.61,-1208.92" />
<ellipse fill="none" stroke="black" cx="2980.05" cy="-1207.09" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2630.44,-778.58 2631.55,-768.64 2633.54,-768.87 2632.42,-778.8 2630.44,-778.58" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.97,-774.06 " />
<polygon fill="black" stroke="black" points="2635.4,-779.14 2636.52,-769.2 2638.51,-769.42 2637.39,-779.36 2635.4,-779.14" />
<polyline fill="none" stroke="black" points="2634.97,-774.06 2639.94,-774.62 " />
<polygon fill="black" stroke="black" points="2630.45,-776.58 2631.53,-766.64 2633.52,-766.85 2632.44,-776.79 2630.45,-776.58" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.97,-772.04 " />
<polygon fill="black" stroke="black" points="2635.43,-777.12 2636.5,-767.18 2638.49,-767.39 2637.41,-777.33 2635.43,-777.12" />
<polyline fill="none" stroke="black" points="2634.97,-772.04 2639.94,-772.58 " />
<text text-anchor="start" x="2714.5" y="-935.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_domain_history</text>
</g> <!-- billingevent_a57d1815&#45;&gt;domainhistory_a54cc226 -->
<g id="edge32" class="edge">
<title>
billingevent_a57d1815:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M2976.8,-1223.36C2971.31,-1207.82 2984.56,-1176.21 2966,-1158.5 2861.76,-1059 2749.2,-1173.41 2656,-1063.5 2630.4,-1033.31 2665.89,-897.37 2639.68,-873.19" />
<polygon fill="black" stroke="black" points="2983.86,-1227.47 2990.24,-1236.39 2988.18,-1229.98 2992.5,-1232.5 2992.5,-1232.5 2992.5,-1232.5 2988.18,-1229.98 2994.76,-1228.61 2983.86,-1227.47 2983.86,-1227.47" />
<ellipse fill="none" stroke="black" cx="2980.4" cy="-1225.46" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2629.16,-874.53 2632.71,-865.18 2634.58,-865.89 2631.03,-875.24 2629.16,-874.53" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.67,-871.28 " />
<polygon fill="black" stroke="black" points="2633.83,-876.31 2637.39,-866.96 2639.26,-867.67 2635.7,-877.02 2633.83,-876.31" />
<polyline fill="none" stroke="black" points="2634.67,-871.28 2639.35,-873.06 " />
<text text-anchor="start" x="2714.5" y="-1162.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_domain_history</text>
<path fill="none" stroke="black" d="M2976.66,-1223.23C2971.2,-1207.48 2984.77,-1175.44 2966,-1157.5 2861.92,-1058.02 2749.2,-1173.23 2656,-1063.5 2630,-1032.89 2666.52,-894.48 2639.43,-870.92" />
<polygon fill="black" stroke="black" points="2983.87,-1227.45 2990.23,-1236.38 2988.18,-1229.97 2992.5,-1232.5 2992.5,-1232.5 2992.5,-1232.5 2988.18,-1229.97 2994.77,-1228.62 2983.87,-1227.45 2983.87,-1227.45" />
<ellipse fill="none" stroke="black" cx="2980.42" cy="-1225.43" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2629.24,-872.54 2632.64,-863.14 2634.52,-863.82 2631.12,-873.22 2629.24,-872.54" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.7,-869.2 " />
<polygon fill="black" stroke="black" points="2633.94,-874.24 2637.34,-864.84 2639.22,-865.52 2635.82,-874.93 2633.94,-874.24" />
<polyline fill="none" stroke="black" points="2634.7,-869.2 2639.4,-870.91 " />
<text text-anchor="start" x="2714.5" y="-1161.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_domain_history</text>
</g> <!-- billingevent_a57d1815&#45;&gt;domainhistory_a54cc226 -->
<g id="edge33" class="edge">
<title>
billingevent_a57d1815:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M2976.93,-1204.14C2952.84,-1163.02 3000.98,-998.93 2966,-965.5 2916.11,-917.81 2706.9,-993.11 2656,-946.5 2630.13,-922.81 2661.43,-801.01 2639.23,-777.45" />
<path fill="none" stroke="black" d="M2976.93,-1204.14C2952.84,-1163.02 3000.98,-998.93 2966,-965.5 2916.11,-917.81 2706.86,-993.15 2656,-946.5 2629.86,-922.52 2661.74,-799.33 2639.33,-775.49" />
<polygon fill="black" stroke="black" points="2983.93,-1208.35 2990.18,-1217.36 2988.21,-1210.92 2992.5,-1213.5 2992.5,-1213.5 2992.5,-1213.5 2988.21,-1210.92 2994.82,-1209.64 2983.93,-1208.35 2983.93,-1208.35" />
<ellipse fill="none" stroke="black" cx="2980.5" cy="-1206.29" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2628.95,-778.49 2632.89,-769.3 2634.73,-770.08 2630.79,-779.28 2628.95,-778.49" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.6,-775.47 " />
<polygon fill="black" stroke="black" points="2633.55,-780.46 2637.48,-771.26 2639.32,-772.05 2635.39,-781.24 2633.55,-780.46" />
<polyline fill="none" stroke="black" points="2634.6,-775.47 2639.19,-777.43 " />
<polygon fill="black" stroke="black" points="2628.95,-776.49 2632.89,-767.3 2634.73,-768.09 2630.79,-777.28 2628.95,-776.49" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.6,-773.47 " />
<polygon fill="black" stroke="black" points="2633.55,-778.46 2637.48,-769.27 2639.32,-770.05 2635.38,-779.25 2633.55,-778.46" />
<polyline fill="none" stroke="black" points="2634.6,-773.47 2639.19,-775.44 " />
<text text-anchor="start" x="2704.5" y="-969.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_recurrence_history</text>
</g> <!-- billingevent_a57d1815&#45;&gt;domainhistory_a54cc226 -->
<g id="edge34" class="edge">
<title>
billingevent_a57d1815:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M2978.52,-1087.04C2975.97,-1074.58 2980.08,-1055.17 2966,-1045.5 2908.95,-1006.32 2706.85,-1062.45 2656,-1015.5 2610.41,-973.41 2686.03,-881.18 2640.06,-870.51" />
<path fill="none" stroke="black" d="M2978.52,-1087.04C2975.97,-1074.58 2980.08,-1055.17 2966,-1045.5 2908.95,-1006.32 2706.8,-1062.51 2656,-1015.5 2609.86,-972.8 2686.74,-879.33 2640.19,-868.52" />
<polygon fill="black" stroke="black" points="2984.77,-1092.16 2989.65,-1101.98 2988.63,-1095.33 2992.5,-1098.5 2992.5,-1098.5 2992.5,-1098.5 2988.63,-1095.33 2995.35,-1095.02 2984.77,-1092.16 2984.77,-1092.16" />
<ellipse fill="none" stroke="black" cx="2981.67" cy="-1089.62" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2630.49,-874.57 2631.5,-864.63 2633.49,-864.83 2632.48,-874.78 2630.49,-874.57" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.97,-870 " />
<polygon fill="black" stroke="black" points="2635.47,-875.08 2636.47,-865.13 2638.46,-865.33 2637.46,-875.28 2635.47,-875.08" />
<polyline fill="none" stroke="black" points="2634.97,-870 2639.95,-870.5 " />
<polygon fill="black" stroke="black" points="2630.49,-872.58 2631.5,-862.63 2633.49,-862.83 2632.48,-872.78 2630.49,-872.58" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.97,-868 " />
<polygon fill="black" stroke="black" points="2635.47,-873.08 2636.47,-863.13 2638.46,-863.33 2637.46,-873.28 2635.47,-873.08" />
<polyline fill="none" stroke="black" points="2634.97,-868 2639.95,-868.5 " />
<text text-anchor="start" x="2704.5" y="-1049.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_recurrence_history</text>
</g> <!-- billingrecurrence_5fa2cb01 -->
<g id="node7" class="node">
@@ -510,14 +510,14 @@ td.section {
<title>
billingevent_a57d1815:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M2974.21,-1251.49C2582,-1251.17 2476.21,-1239.98 2078,-1191.5 1897.39,-1169.51 1856.24,-1135.56 1675,-1119.5 1561.67,-1109.46 1532.65,-1114.14 1419,-1119.5 1286.34,-1125.76 322.69,-1155.41 231,-1251.5 194.01,-1290.26 253.88,-1480.65 215.3,-1505.76" />
<path fill="none" stroke="black" d="M2974.21,-1251.49C2582,-1251.15 2476.27,-1239.5 2078,-1191.5 1897.44,-1169.74 1856.18,-1136.34 1675,-1120.5 1561.65,-1110.59 1532.66,-1115.3 1419,-1120.5 1286.34,-1126.57 322.68,-1155.42 231,-1251.5 194.01,-1290.26 253.88,-1480.65 215.3,-1505.76" />
<polygon fill="black" stroke="black" points="2982.5,-1251.5 2992.5,-1256 2987.5,-1251.5 2992.5,-1251.5 2992.5,-1251.5 2992.5,-1251.5 2987.5,-1251.5 2992.5,-1247 2982.5,-1251.5 2982.5,-1251.5" />
<ellipse fill="none" stroke="black" cx="2978.5" cy="-1251.49" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="207.81,-1513.05 205.12,-1503.42 207.04,-1502.88 209.73,-1512.51 207.81,-1513.05" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 210.32,-1507.15 " />
<polygon fill="black" stroke="black" points="212.62,-1511.7 209.93,-1502.07 211.86,-1501.53 214.55,-1511.16 212.62,-1511.7" />
<polyline fill="none" stroke="black" points="210.32,-1507.15 215.13,-1505.81 " />
<text text-anchor="start" x="1462.5" y="-1123.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_registrar_id</text>
<text text-anchor="start" x="1462.5" y="-1124.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_event_registrar_id</text>
</g> <!-- domain_6c51cffa -->
<g id="node3" class="node">
<title>
@@ -596,14 +596,14 @@ td.section {
<title>
domain_6c51cffa:w-&gt;allocationtoken_a08ccbef:e
</title>
<path fill="none" stroke="black" d="M1696.58,-1231.04C1660.47,-1212.8 1718.44,-1126.97 1675,-1082.5 1592.33,-997.86 1495.93,-1099.39 1419,-1009.5 1376.49,-959.83 1426.77,-923.59 1401,-863.5 1393.04,-844.94 1391.16,-828.06 1376.65,-823.02" />
<path fill="none" stroke="black" d="M1696.68,-1231.06C1660.76,-1212.94 1718.2,-1127.66 1675,-1083.5 1592.26,-998.93 1495.89,-1100.42 1419,-1010.5 1376.22,-960.47 1426.92,-924 1401,-863.5 1393.05,-844.94 1391.16,-828.06 1376.65,-823.02" />
<polygon fill="black" stroke="black" points="1704.68,-1232.6 1713.65,-1238.92 1709.59,-1233.55 1714.5,-1234.5 1714.5,-1234.5 1714.5,-1234.5 1709.59,-1233.55 1715.35,-1230.08 1704.68,-1232.6 1704.68,-1232.6" />
<ellipse fill="none" stroke="black" cx="1700.75" cy="-1231.85" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1366.75,-826.59 1368.23,-816.7 1370.21,-817 1368.73,-826.89 1366.75,-826.59" />
<polyline fill="none" stroke="black" points="1366.5,-821.5 1371.44,-822.24 " />
<polygon fill="black" stroke="black" points="1371.69,-827.33 1373.18,-817.45 1375.15,-817.74 1373.67,-827.63 1371.69,-827.33" />
<polyline fill="none" stroke="black" points="1371.44,-822.24 1376.39,-822.98 " />
<text text-anchor="start" x="1441" y="-1086.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_current_package_token</text>
<text text-anchor="start" x="1441" y="-1087.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_current_package_token</text>
</g> <!-- domain_6c51cffa&#45;&gt;billingevent_a57d1815 -->
<g id="edge6" class="edge">
<title>
@@ -656,27 +656,27 @@ td.section {
<title>
domain_6c51cffa:w-&gt;billingcancellation_6eedf614:e
</title>
<path fill="none" stroke="black" d="M1696.61,-1364.24C1657.67,-1345.95 1720.61,-1255.2 1675,-1208.5 1592.58,-1124.12 1500.81,-1223.47 1419,-1138.5 1384.42,-1102.59 1436.45,-1032.03 1401.64,-1021.76" />
<path fill="none" stroke="black" d="M1696.71,-1364.26C1657.97,-1346.09 1720.38,-1255.89 1675,-1209.5 1592.51,-1125.18 1500.73,-1224.55 1419,-1139.5 1384.18,-1103.26 1436.79,-1032.13 1401.72,-1021.77" />
<polygon fill="black" stroke="black" points="1704.66,-1365.71 1713.69,-1371.93 1709.58,-1366.6 1714.5,-1367.5 1714.5,-1367.5 1714.5,-1367.5 1709.58,-1366.6 1715.31,-1363.07 1704.66,-1365.71 1704.66,-1365.71" />
<ellipse fill="none" stroke="black" cx="1700.73" cy="-1364.99" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1391.88,-1025.59 1393.11,-1015.66 1395.09,-1015.91 1393.86,-1025.83 1391.88,-1025.59" />
<polyline fill="none" stroke="black" points="1391.5,-1020.5 1396.46,-1021.12 " />
<polygon fill="black" stroke="black" points="1396.84,-1026.2 1398.07,-1016.28 1400.06,-1016.53 1398.82,-1026.45 1396.84,-1026.2" />
<polyline fill="none" stroke="black" points="1396.46,-1021.12 1401.42,-1021.73 " />
<text text-anchor="start" x="1419" y="-1212.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_transfer_billing_cancellation_id</text>
<text text-anchor="start" x="1419" y="-1213.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_transfer_billing_cancellation_id</text>
</g> <!-- domain_6c51cffa&#45;&gt;billingrecurrence_5fa2cb01 -->
<g id="edge10" class="edge">
<title>
domain_6c51cffa:w-&gt;billingrecurrence_5fa2cb01:e
</title>
<path fill="none" stroke="black" d="M1697.22,-1267.32C1677.12,-1249.49 1709.18,-1190.36 1675,-1163.5 1630,-1128.14 1475.52,-1144.48 1419,-1135.5 1262.5,-1110.63 1218.4,-1118.09 1070,-1062.5 935.47,-1012.1 875.64,-1018.34 788,-904.5 762.77,-871.73 799.98,-816.6 772.61,-806.96" />
<polygon fill="black" stroke="black" points="1704.92,-1269.63 1713.21,-1276.81 1709.71,-1271.06 1714.5,-1272.5 1714.5,-1272.5 1714.5,-1272.5 1709.71,-1271.06 1715.79,-1268.19 1704.92,-1269.63 1704.92,-1269.63" />
<ellipse fill="none" stroke="black" cx="1701.09" cy="-1268.48" rx="4" ry="4" />
<path fill="none" stroke="black" d="M1697.06,-1267.11C1677.63,-1249.16 1708.79,-1190.97 1675,-1164.5 1629.95,-1129.21 1475.51,-1145.56 1419,-1136.5 1262.44,-1111.4 1218.41,-1118.33 1070,-1062.5 935.54,-1011.91 875.64,-1018.34 788,-904.5 762.77,-871.73 799.98,-816.6 772.61,-806.96" />
<polygon fill="black" stroke="black" points="1704.95,-1269.55 1713.17,-1276.8 1709.72,-1271.02 1714.5,-1272.5 1714.5,-1272.5 1714.5,-1272.5 1709.72,-1271.02 1715.83,-1268.2 1704.95,-1269.55 1704.95,-1269.55" />
<ellipse fill="none" stroke="black" cx="1701.12" cy="-1268.37" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="762.78,-810.59 764.2,-800.69 766.18,-800.98 764.75,-810.88 762.78,-810.59" />
<polyline fill="none" stroke="black" points="762.5,-805.5 767.45,-806.21 " />
<polygon fill="black" stroke="black" points="767.72,-811.31 769.15,-801.41 771.13,-801.69 769.7,-811.59 767.72,-811.31" />
<polyline fill="none" stroke="black" points="767.45,-806.21 772.4,-806.93 " />
<text text-anchor="start" x="1137.5" y="-1136.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_billing_recurrence_id</text>
<text text-anchor="start" x="1137.5" y="-1137.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_billing_recurrence_id</text>
</g> <!-- domain_6c51cffa&#45;&gt;billingrecurrence_5fa2cb01 -->
<g id="edge11" class="edge">
<title>
@@ -878,22 +878,22 @@ td.section {
<title>
domainhistory_a54cc226:w-&gt;allocationtoken_a08ccbef:e
</title>
<path fill="none" stroke="black" d="M2317.97,-754.39C2209.77,-753.1 2176.62,-740.3 2060,-736.5 1896.98,-731.19 1855.88,-727.9 1693,-736.5 1662.43,-738.11 1446.41,-751.87 1419,-765.5 1391.91,-778.97 1398.41,-812.58 1376.44,-820.03" />
<polygon fill="black" stroke="black" points="2326,-754.44 2335.97,-759 2331,-754.47 2336,-754.5 2336,-754.5 2336,-754.5 2331,-754.47 2336.03,-750 2326,-754.44 2326,-754.44" />
<ellipse fill="none" stroke="black" cx="2322" cy="-754.42" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1368.22,-826.3 1366.76,-816.41 1368.73,-816.11 1370.2,-826.01 1368.22,-826.3" />
<path fill="none" stroke="black" d="M2317.97,-752.39C2209.77,-751.1 2176.62,-738.3 2060,-734.5 1896.98,-729.19 1855.56,-721.05 1693,-734.5 1684.92,-735.17 1683.03,-736.39 1675,-737.5 1561.62,-753.14 1521.39,-714.35 1419,-765.5 1391.94,-779.02 1398.41,-812.59 1376.44,-820.03" />
<polygon fill="black" stroke="black" points="2326,-752.44 2335.97,-757 2331,-752.47 2336,-752.5 2336,-752.5 2336,-752.5 2331,-752.47 2336.03,-748 2326,-752.44 2326,-752.44" />
<ellipse fill="none" stroke="black" cx="2322" cy="-752.42" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1368.22,-826.3 1366.76,-816.41 1368.74,-816.11 1370.2,-826.01 1368.22,-826.3" />
<polyline fill="none" stroke="black" points="1366.5,-821.5 1371.45,-820.77 " />
<polygon fill="black" stroke="black" points="1373.17,-825.57 1371.7,-815.67 1373.68,-815.38 1375.15,-825.27 1373.17,-825.57" />
<polyline fill="none" stroke="black" points="1371.45,-820.77 1376.39,-820.03 " />
<text text-anchor="start" x="1747" y="-740.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_history_current_package_token</text>
<text text-anchor="start" x="1747" y="-738.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_domain_history_current_package_token</text>
</g> <!-- domainhistory_a54cc226&#45;&gt;domain_6c51cffa -->
<g id="edge24" class="edge">
<title>
domainhistory_a54cc226:w-&gt;domain_6c51cffa:e
</title>
<path fill="none" stroke="black" d="M2324.79,-787.9C2306.79,-849.87 2333.2,-1091.76 2310,-1115.5 2273.79,-1152.55 2114.36,-1101.59 2078,-1138.5 2044.47,-1172.53 2084.68,-1543.75 2048.18,-1591.46" />
<polygon fill="black" stroke="black" points="2329.86,-781.39 2339.55,-776.26 2332.93,-777.45 2336,-773.5 2336,-773.5 2336,-773.5 2332.93,-777.45 2332.45,-770.74 2329.86,-781.39 2329.86,-781.39" />
<ellipse fill="none" stroke="black" cx="2327.4" cy="-784.55" rx="4" ry="4" />
<path fill="none" stroke="black" d="M2324.73,-785.98C2306.66,-848.31 2333.33,-1091.62 2310,-1115.5 2273.8,-1152.56 2114.36,-1101.59 2078,-1138.5 2044.47,-1172.53 2084.68,-1543.75 2048.18,-1591.46" />
<polygon fill="black" stroke="black" points="2329.86,-779.39 2339.55,-774.26 2332.93,-775.45 2336,-771.5 2336,-771.5 2336,-771.5 2332.93,-775.45 2332.45,-768.74 2329.86,-779.39 2329.86,-779.39" />
<ellipse fill="none" stroke="black" cx="2327.4" cy="-782.55" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2042.87,-1600.32 2037.86,-1591.67 2039.59,-1590.67 2044.6,-1599.32 2042.87,-1600.32" />
<polyline fill="none" stroke="black" points="2039.5,-1596.5 2043.82,-1593.99 " />
<polygon fill="black" stroke="black" points="2047.2,-1597.81 2042.18,-1589.16 2043.91,-1588.16 2048.93,-1596.81 2047.2,-1597.81" />
@@ -904,9 +904,9 @@ td.section {
<title>
domainhistory_a54cc226:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M2317.98,-849.23C2213.9,-841.75 1694.56,-803.77 1693,-802.5 1670.42,-784.11 1697.19,-758.36 1675,-739.5 1581.57,-660.11 1522.93,-720.32 1401,-707.5 1120.88,-678.05 1051.36,-661.63 770,-648.5 627.49,-641.85 586.15,-609.23 449,-648.5 339.22,-679.94 288.15,-685.63 231,-784.5 212.12,-817.17 241.6,-1424.91 212.2,-1500.75" />
<polygon fill="black" stroke="black" points="2326.02,-849.8 2335.68,-854.99 2331.01,-850.15 2336,-850.5 2336,-850.5 2336,-850.5 2331.01,-850.15 2336.32,-846.01 2326.02,-849.8 2326.02,-849.8" />
<ellipse fill="none" stroke="black" cx="2322.03" cy="-849.51" rx="4" ry="4" />
<path fill="none" stroke="black" d="M2317.98,-847.23C2213.9,-839.75 1694.57,-801.77 1693,-800.5 1671.01,-782.74 1696.62,-757.71 1675,-739.5 1581.23,-660.51 1522.93,-720.32 1401,-707.5 1120.88,-678.05 1051.36,-661.63 770,-648.5 627.49,-641.85 586.15,-609.23 449,-648.5 339.22,-679.94 288.15,-685.63 231,-784.5 212.12,-817.17 241.6,-1424.91 212.2,-1500.75" />
<polygon fill="black" stroke="black" points="2326.02,-847.8 2335.68,-852.99 2331.01,-848.15 2336,-848.5 2336,-848.5 2336,-848.5 2331.01,-848.15 2336.32,-844.01 2326.02,-847.8 2326.02,-847.8" />
<ellipse fill="none" stroke="black" cx="2322.03" cy="-847.51" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="209.94,-1511.01 202.37,-1504.47 203.68,-1502.96 211.24,-1509.5 209.94,-1511.01" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 208.77,-1504.72 " />
<polygon fill="black" stroke="black" points="213.21,-1507.23 205.64,-1500.69 206.95,-1499.18 214.51,-1505.72 213.21,-1507.23" />
@@ -917,7 +917,7 @@ td.section {
<title>
billingcancellation_6eedf614:w-&gt;billingevent_a57d1815:e
</title>
<path fill="none" stroke="black" d="M1072.35,-889.39C1073.82,-883.45 1077.2,-877.17 1081.5,-873 1094.16,-860.72 1385.84,-861.26 1399,-873 1428.13,-898.98 1392.03,-1019.28 1419,-1047.5 1431.81,-1060.9 2059.68,-1150.66 2078,-1153.5 2472.31,-1214.52 2610.87,-1103.61 2966,-1285.5 2976.21,-1290.73 2973.43,-1299.54 2984,-1304 3005.21,-1312.96 3381.98,-1320.04 3398.5,-1304 3405.85,-1296.87 3410.61,-1283.71 3408.02,-1276.18" />
<path fill="none" stroke="black" d="M1072.35,-889.39C1073.82,-883.45 1077.2,-877.17 1081.5,-873 1094.16,-860.72 1385.84,-861.25 1399,-873 1428.28,-899.14 1391.87,-1020.13 1419,-1048.5 1431.81,-1061.9 2059.68,-1150.68 2078,-1153.5 2472.36,-1214.23 2610.87,-1103.61 2966,-1285.5 2976.21,-1290.73 2973.43,-1299.54 2984,-1304 3005.21,-1312.96 3381.98,-1320.04 3398.5,-1304 3405.85,-1296.87 3410.61,-1283.71 3408.02,-1276.18" />
<polygon fill="black" stroke="black" points="1075.98,-896.58 1076.48,-907.53 1078.24,-901.04 1080.5,-905.5 1080.5,-905.5 1080.5,-905.5 1078.24,-901.04 1084.52,-903.47 1075.98,-896.58 1075.98,-896.58" />
<ellipse fill="none" stroke="black" cx="1074.18" cy="-893.01" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="3397.56,-1275.22 3403.11,-1266.89 3404.77,-1268 3399.22,-1276.32 3397.56,-1275.22" />
@@ -930,26 +930,26 @@ td.section {
<title>
billingcancellation_6eedf614:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M1069.72,-948.03C1065.01,-925.43 1071.43,-883.26 1081.5,-873 1093.94,-860.33 1383.29,-864.77 1401,-863.5 1806.59,-834.32 1946.21,-941.18 2310,-759.5 2320.41,-754.3 2317.31,-745.1 2328,-740.5 2450.89,-687.64 2532.98,-647.35 2629,-740.5 2636.24,-747.52 2640.93,-760.49 2638.39,-767.9" />
<path fill="none" stroke="black" d="M1069.72,-948.03C1065.01,-925.43 1071.43,-883.26 1081.5,-873 1093.94,-860.33 1383.29,-864.79 1401,-863.5 1806.66,-833.87 1946.31,-939.62 2310,-757.5 2320.4,-752.29 2317.31,-743.1 2328,-738.5 2450.89,-685.64 2532.98,-645.35 2629,-738.5 2636.24,-745.52 2640.93,-758.49 2638.39,-765.9" />
<polygon fill="black" stroke="black" points="1074.53,-954.48 1076.89,-965.19 1077.51,-958.49 1080.5,-962.5 1080.5,-962.5 1080.5,-962.5 1077.51,-958.49 1084.11,-959.81 1074.53,-954.48 1074.53,-954.48" />
<ellipse fill="none" stroke="black" cx="1072.14" cy="-951.27" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2633.61,-777.11 2628.06,-768.79 2629.72,-767.68 2635.27,-776 2633.61,-777.11" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.16,-770.73 " />
<polygon fill="black" stroke="black" points="2637.77,-774.33 2632.22,-766.01 2633.88,-764.9 2639.43,-773.22 2637.77,-774.33" />
<polyline fill="none" stroke="black" points="2634.16,-770.73 2638.32,-767.95 " />
<text text-anchor="start" x="1760.5" y="-873.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_cancellation_domain_history</text>
<polygon fill="black" stroke="black" points="2633.61,-775.11 2628.06,-766.79 2629.72,-765.68 2635.27,-774 2633.61,-775.11" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.16,-768.73 " />
<polygon fill="black" stroke="black" points="2637.77,-772.33 2632.22,-764.01 2633.88,-762.9 2639.43,-771.22 2637.77,-772.33" />
<polyline fill="none" stroke="black" points="2634.16,-768.73 2638.32,-765.95 " />
<text text-anchor="start" x="1760.5" y="-872.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_cancellation_domain_history</text>
</g> <!-- billingcancellation_6eedf614&#45;&gt;domainhistory_a54cc226 -->
<g id="edge30" class="edge">
<title>
billingcancellation_6eedf614:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M1071.29,-998.07C1069,-1016.69 1074.07,-1046.33 1081.5,-1054 1106.23,-1079.52 1365.77,-1063.82 1401,-1068.5 1409.12,-1069.58 1410.85,-1071.61 1419,-1072.5 1532.1,-1084.92 1561.28,-1075.99 1675,-1072.5 1957.62,-1063.83 2034.4,-1096.72 2310,-1033.5 2459.39,-999.23 2541.74,-1028.5 2629,-902.5 2634.55,-894.49 2639.64,-882.57 2638.14,-875.46" />
<path fill="none" stroke="black" d="M1071.29,-998.07C1069,-1016.69 1074.07,-1046.33 1081.5,-1054 1106.23,-1079.52 1365.91,-1062.9 1401,-1068.5 1409.2,-1069.81 1410.77,-1072.38 1419,-1073.5 1531.74,-1088.87 1561.28,-1077.17 1675,-1073.5 1957.66,-1064.38 2034.54,-1096.54 2310,-1032.5 2459.45,-997.76 2541.81,-1026.76 2629,-900.5 2634.54,-892.48 2639.63,-880.56 2638.13,-873.46" />
<polygon fill="black" stroke="black" points="1075.41,-991.11 1084.37,-984.79 1077.95,-986.8 1080.5,-982.5 1080.5,-982.5 1080.5,-982.5 1077.95,-986.8 1076.63,-980.21 1075.41,-991.11 1075.41,-991.11" />
<ellipse fill="none" stroke="black" cx="1073.37" cy="-994.55" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2627.85,-874.12 2633.76,-866.06 2635.37,-867.24 2629.47,-875.31 2627.85,-874.12" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.03,-872.45 " />
<polygon fill="black" stroke="black" points="2631.89,-877.08 2637.79,-869.01 2639.41,-870.19 2633.5,-878.26 2631.89,-877.08" />
<polyline fill="none" stroke="black" points="2634.03,-872.45 2638.07,-875.41 " />
<polygon fill="black" stroke="black" points="2627.85,-872.12 2633.76,-864.06 2635.37,-865.24 2629.47,-873.31 2627.85,-872.12" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.03,-870.45 " />
<polygon fill="black" stroke="black" points="2631.89,-875.08 2637.79,-867.01 2639.41,-868.19 2633.5,-876.26 2631.89,-875.08" />
<polyline fill="none" stroke="black" points="2634.03,-870.45 2638.07,-873.41 " />
<text text-anchor="start" x="1760.5" y="-1078.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_cancellation_domain_history</text>
</g> <!-- billingcancellation_6eedf614&#45;&gt;billingrecurrence_5fa2cb01 -->
<g id="edge8" class="edge">
@@ -1059,27 +1059,27 @@ td.section {
<title>
billingrecurrence_5fa2cb01:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M446.69,-733C441.92,-710.35 448.28,-668.11 458.5,-658 507.72,-609.33 700.79,-656.99 770,-658 1172.35,-663.89 1272.99,-666.82 1675,-684.5 1887.16,-693.83 2480.98,-588.22 2629,-740.5 2636.03,-747.73 2640.8,-760.62 2638.33,-767.97" />
<path fill="none" stroke="black" d="M446.69,-733C441.92,-710.35 448.28,-668.11 458.5,-658 507.72,-609.33 700.79,-656.99 770,-658 1172.35,-663.89 1272.98,-667.24 1675,-684.5 1887.14,-693.61 2480.84,-586.4 2629,-738.5 2636.04,-745.73 2640.81,-758.61 2638.33,-765.96" />
<polygon fill="black" stroke="black" points="451.52,-739.48 453.89,-750.19 454.51,-743.49 457.5,-747.5 457.5,-747.5 457.5,-747.5 454.51,-743.49 461.11,-744.81 451.52,-739.48 451.52,-739.48" />
<ellipse fill="none" stroke="black" cx="449.13" cy="-736.28" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2633.6,-777.11 2628.07,-768.78 2629.73,-767.68 2635.27,-776 2633.6,-777.11" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.16,-770.73 " />
<polygon fill="black" stroke="black" points="2637.76,-774.34 2632.23,-766.01 2633.9,-764.91 2639.43,-773.24 2637.76,-774.34" />
<polyline fill="none" stroke="black" points="2634.16,-770.73 2638.33,-767.97 " />
<polygon fill="black" stroke="black" points="2633.6,-775.11 2628.07,-766.78 2629.73,-765.68 2635.27,-774 2633.6,-775.11" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.16,-768.73 " />
<polygon fill="black" stroke="black" points="2637.76,-772.34 2632.23,-764.01 2633.9,-762.91 2639.43,-771.24 2637.76,-772.34" />
<polyline fill="none" stroke="black" points="2634.16,-768.73 2638.33,-765.97 " />
<text text-anchor="start" x="1434.5" y="-688.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_recurrence_domain_history</text>
</g> <!-- billingrecurrence_5fa2cb01&#45;&gt;domainhistory_a54cc226 -->
<g id="edge36" class="edge">
<title>
billingrecurrence_5fa2cb01:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M448.24,-783.12C445.89,-801.8 450.9,-831.5 458.5,-839 557.08,-936.21 652.03,-911.45 770,-839 785.07,-829.74 774.03,-814.36 788,-803.5 889.4,-724.69 942.35,-754.58 1070,-740.5 1216.22,-724.37 1255.53,-718.59 1401,-740.5 1535.29,-760.73 1560.07,-802.73 1693,-830.5 1971.03,-888.58 2044.42,-886.47 2328,-902.5 2461.56,-910.05 2532.98,-995.65 2629,-902.5 2636.24,-895.48 2640.93,-882.51 2638.39,-875.1" />
<path fill="none" stroke="black" d="M448.24,-783.12C445.89,-801.8 450.9,-831.5 458.5,-839 557.08,-936.21 652.03,-911.45 770,-839 785.07,-829.74 774.03,-814.36 788,-803.5 889.4,-724.69 942.35,-754.58 1070,-740.5 1216.22,-724.37 1255.46,-719.05 1401,-740.5 1535.09,-760.27 1560.23,-801.2 1693,-828.5 1971.21,-885.71 2044.42,-884.47 2328,-900.5 2461.56,-908.05 2532.98,-993.65 2629,-900.5 2636.24,-893.48 2640.93,-880.51 2638.39,-873.1" />
<polygon fill="black" stroke="black" points="452.4,-776.1 461.37,-769.8 454.95,-771.8 457.5,-767.5 457.5,-767.5 457.5,-767.5 454.95,-771.8 453.63,-765.2 452.4,-776.1 452.4,-776.1" />
<ellipse fill="none" stroke="black" cx="450.36" cy="-779.54" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2628.06,-874.21 2633.61,-865.89 2635.27,-867 2629.72,-875.32 2628.06,-874.21" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.16,-872.27 " />
<polygon fill="black" stroke="black" points="2632.22,-876.99 2637.77,-868.67 2639.43,-869.78 2633.88,-878.1 2632.22,-876.99" />
<polyline fill="none" stroke="black" points="2634.16,-872.27 2638.32,-875.05 " />
<text text-anchor="start" x="1434.5" y="-830.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_recurrence_domain_history</text>
<polygon fill="black" stroke="black" points="2628.06,-872.21 2633.61,-863.89 2635.27,-865 2629.72,-873.32 2628.06,-872.21" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.16,-870.27 " />
<polygon fill="black" stroke="black" points="2632.22,-874.99 2637.77,-866.67 2639.43,-867.78 2633.88,-876.1 2632.22,-874.99" />
<polyline fill="none" stroke="black" points="2634.16,-870.27 2638.32,-873.05 " />
<text text-anchor="start" x="1434.5" y="-828.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_billing_recurrence_domain_history</text>
</g> <!-- billingrecurrence_5fa2cb01&#45;&gt;registrar_6e1503e3 -->
<g id="edge55" class="edge">
<title>
@@ -1440,26 +1440,26 @@ td.section {
<title>
pollmessage_614a523e:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3635.14,-1709.39C3618.47,-1694.46 3640.72,-1649.84 3626,-1615.5 3558.49,-1458.01 3475.42,-1450.27 3425,-1286.5 3412.95,-1247.37 3435.42,-947.96 3407,-918.5 3341.5,-850.6 3074.81,-908.05 2984,-882.5 2975.39,-880.08 2974.6,-875.94 2966,-873.5 2899.46,-854.59 2710.76,-887.75 2656,-845.5 2632.43,-827.31 2656.25,-785.83 2639.78,-775.74" />
<path fill="none" stroke="black" d="M3635.14,-1709.39C3618.47,-1694.46 3640.72,-1649.84 3626,-1615.5 3558.49,-1458.01 3475.42,-1450.27 3425,-1286.5 3412.95,-1247.37 3435.42,-947.96 3407,-918.5 3341.5,-850.6 3074.81,-908.05 2984,-882.5 2975.39,-880.08 2974.6,-875.94 2966,-873.5 2899.46,-854.59 2710.58,-887.99 2656,-845.5 2631.93,-826.76 2656.77,-784.16 2640,-773.8" />
<polygon fill="black" stroke="black" points="3642.91,-1711.67 3651.23,-1718.82 3647.7,-1713.09 3652.5,-1714.5 3652.5,-1714.5 3652.5,-1714.5 3647.7,-1713.09 3653.77,-1710.18 3642.91,-1711.67 3642.91,-1711.67" />
<ellipse fill="none" stroke="black" cx="3639.07" cy="-1710.54" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2629.86,-778.6 2632.09,-768.85 2634.04,-769.3 2631.81,-779.04 2629.86,-778.6" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.87,-774.62 " />
<polygon fill="black" stroke="black" points="2634.73,-779.71 2636.97,-769.97 2638.91,-770.41 2636.68,-780.16 2634.73,-779.71" />
<polyline fill="none" stroke="black" points="2634.87,-774.62 2639.75,-775.73 " />
<polygon fill="black" stroke="black" points="2629.85,-776.6 2632.1,-766.85 2634.05,-767.3 2631.8,-777.05 2629.85,-776.6" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.87,-772.62 " />
<polygon fill="black" stroke="black" points="2634.73,-777.72 2636.97,-767.97 2638.92,-768.42 2636.67,-778.17 2634.73,-777.72" />
<polyline fill="none" stroke="black" points="2634.87,-772.62 2639.75,-773.74 " />
<text text-anchor="start" x="3094.5" y="-922.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_poll_message_domain_history</text>
</g> <!-- pollmessage_614a523e&#45;&gt;domainhistory_a54cc226 -->
<g id="edge46" class="edge">
<title>
pollmessage_614a523e:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3636.21,-1686.03C3629.34,-1670.28 3644.54,-1636.18 3626,-1615.5 3410.32,-1374.87 3260.46,-1443.59 2966,-1310.5 2828.74,-1248.46 2743.52,-1296.1 2656,-1173.5 2638.11,-1148.44 2659.23,-918.15 2637.87,-876.08" />
<path fill="none" stroke="black" d="M3636.21,-1686.03C3629.34,-1670.28 3644.54,-1636.18 3626,-1615.5 3410.32,-1374.87 3260.28,-1443.99 2966,-1310.5 2828.66,-1248.2 2743.44,-1295.37 2656,-1172.5 2638.05,-1147.28 2659.41,-915.31 2637.75,-873.83" />
<polygon fill="black" stroke="black" points="3643.63,-1689.89 3650.42,-1698.49 3648.06,-1692.19 3652.5,-1694.5 3652.5,-1694.5 3652.5,-1694.5 3648.06,-1692.19 3654.58,-1690.51 3643.63,-1689.89 3643.63,-1689.89" />
<ellipse fill="none" stroke="black" cx="3640.08" cy="-1688.04" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2627.56,-873.98 2633.98,-866.31 2635.51,-867.59 2629.09,-875.26 2627.56,-873.98" />
<polyline fill="none" stroke="black" points="2630,-869.5 2633.83,-872.71 " />
<polygon fill="black" stroke="black" points="2631.39,-877.19 2637.81,-869.52 2639.34,-870.8 2632.93,-878.47 2631.39,-877.19" />
<polyline fill="none" stroke="black" points="2633.83,-872.71 2637.67,-875.92 " />
<polygon fill="black" stroke="black" points="2627.61,-872 2633.94,-864.26 2635.49,-865.53 2629.16,-873.27 2627.61,-872" />
<polyline fill="none" stroke="black" points="2630,-867.5 2633.87,-870.66 " />
<polygon fill="black" stroke="black" points="2631.48,-875.17 2637.81,-867.42 2639.36,-868.69 2633.03,-876.43 2631.48,-875.17" />
<polyline fill="none" stroke="black" points="2633.87,-870.66 2637.74,-873.83 " />
<text text-anchor="start" x="3094.5" y="-1454.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_poll_message_domain_history</text>
</g> <!-- pollmessage_614a523e&#45;&gt;contact_8de8cb16 -->
<g id="edge20" class="edge">
@@ -1625,20 +1625,20 @@ td.section {
<title>
pollmessage_614a523e:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M3634.24,-1616.43C3631.94,-1616.06 3629.33,-1615.73 3626,-1615.5 3333.26,-1595.64 3259.39,-1603.83 2966,-1600.5 2828.23,-1598.93 2778.63,-1537.69 2656,-1600.5 2643.81,-1606.74 2649.95,-1618.82 2638,-1625.5 2529.1,-1686.41 2191.46,-1614.57 2078,-1666.5 2067.71,-1671.21 2070.13,-1679.47 2060,-1684.5 1957.13,-1735.55 1662.84,-1754.5 1548,-1754.5 1548,-1754.5 1548,-1754.5 608.5,-1754.5 524.44,-1754.5 291.93,-1788.41 231,-1730.5 197.82,-1698.96 246.35,-1536.9 215.23,-1511.78" />
<path fill="none" stroke="black" d="M3634.24,-1616.43C3631.94,-1616.06 3629.33,-1615.73 3626,-1615.5 3333.26,-1595.64 3259.39,-1603.83 2966,-1600.5 2828.23,-1598.93 2778.63,-1537.69 2656,-1600.5 2643.81,-1606.74 2649.99,-1618.88 2638,-1625.5 2528.85,-1685.78 2187.73,-1601.29 2078,-1660.5 2066.27,-1666.83 2071.49,-1677.74 2060,-1684.5 1961.01,-1742.71 1662.84,-1754.5 1548,-1754.5 1548,-1754.5 1548,-1754.5 608.5,-1754.5 524.44,-1754.5 291.93,-1788.41 231,-1730.5 197.82,-1698.96 246.35,-1536.9 215.23,-1511.78" />
<polygon fill="black" stroke="black" points="3642.56,-1617.37 3651.99,-1622.97 3647.53,-1617.94 3652.5,-1618.5 3652.5,-1618.5 3652.5,-1618.5 3647.53,-1617.94 3653.01,-1614.03 3642.56,-1617.37 3642.56,-1617.37" />
<ellipse fill="none" stroke="black" cx="3638.59" cy="-1616.92" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="204.85,-1513.56 208.04,-1504.08 209.94,-1504.72 206.75,-1514.2 204.85,-1513.56" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 210.24,-1510.1 " />
<polygon fill="black" stroke="black" points="209.59,-1515.15 212.78,-1505.68 214.68,-1506.32 211.49,-1515.79 209.59,-1515.15" />
<polyline fill="none" stroke="black" points="210.24,-1510.1 214.98,-1511.69 " />
<text text-anchor="start" x="1704" y="-1753.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_poll_message_transfer_response_gaining_registrar_id</text>
<text text-anchor="start" x="1704" y="-1754.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_poll_message_transfer_response_gaining_registrar_id</text>
</g> <!-- pollmessage_614a523e&#45;&gt;registrar_6e1503e3 -->
<g id="edge75" class="edge">
<title>
pollmessage_614a523e:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M3634.32,-1598.69C3526.73,-1588.75 3522.25,-1490.05 3407,-1459.5 3225.28,-1411.33 3132.37,-1344.05 2984,-1459.5 2958.31,-1479.49 2990.46,-1509.02 2966,-1530.5 2963.62,-1532.59 2063.15,-1638.2 2060,-1638.5 1978.6,-1646.34 1764.37,-1625.57 1693,-1665.5 1681.05,-1672.19 1687.05,-1683.99 1675,-1690.5 1567.75,-1748.43 1522.87,-1699.83 1401,-1702.5 1271.02,-1705.34 328.42,-1772.6 231,-1686.5 203.98,-1662.62 237.5,-1536.9 214.94,-1512.58" />
<path fill="none" stroke="black" d="M3634.32,-1598.69C3526.73,-1588.75 3522.25,-1490.05 3407,-1459.5 3225.28,-1411.33 3132.18,-1343.8 2984,-1459.5 2958,-1479.8 2990.78,-1509.73 2966,-1531.5 2963.67,-1533.55 2081.06,-1633.98 2078,-1634.5 2069.92,-1635.86 2068.11,-1637.31 2060,-1638.5 1979.09,-1650.4 1764.37,-1625.57 1693,-1665.5 1681.05,-1672.19 1687.05,-1683.99 1675,-1690.5 1567.75,-1748.43 1522.87,-1699.83 1401,-1702.5 1271.02,-1705.34 328.42,-1772.6 231,-1686.5 203.98,-1662.62 237.5,-1536.9 214.94,-1512.58" />
<polygon fill="black" stroke="black" points="3642.51,-1599.05 3652.3,-1604 3647.5,-1599.28 3652.5,-1599.5 3652.5,-1599.5 3652.5,-1599.5 3647.5,-1599.28 3652.7,-1595 3642.51,-1599.05 3642.51,-1599.05" />
<ellipse fill="none" stroke="black" cx="3638.51" cy="-1598.88" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="204.44,-1513.49 208.4,-1504.31 210.24,-1505.1 206.27,-1514.28 204.44,-1513.49" />
@@ -1768,14 +1768,14 @@ td.section {
<title>
host_f21b78de:w-&gt;domain_6c51cffa:e
</title>
<path fill="none" stroke="black" d="M2331.47,-1696.28C2272.5,-1679.09 2090.96,-1615.01 2078,-1610.5 2064.56,-1605.83 2059.74,-1599.91 2049.58,-1597.55" />
<polygon fill="black" stroke="black" points="2339.28,-1698.16 2347.95,-1704.88 2344.14,-1699.33 2349,-1700.5 2349,-1700.5 2349,-1700.5 2344.14,-1699.33 2350.05,-1696.12 2339.28,-1698.16 2339.28,-1698.16" />
<ellipse fill="none" stroke="black" cx="2335.39" cy="-1697.23" rx="4" ry="4" />
<path fill="none" stroke="black" d="M2330.78,-1700.15C2219.44,-1695.7 2191.9,-1650.11 2078,-1610.5 2064.56,-1605.83 2059.74,-1599.91 2049.58,-1597.55" />
<polygon fill="black" stroke="black" points="2339,-1700.31 2348.91,-1705 2344,-1700.4 2349,-1700.5 2349,-1700.5 2349,-1700.5 2344,-1700.4 2349.09,-1696 2339,-1700.31 2339,-1700.31" />
<ellipse fill="none" stroke="black" cx="2335" cy="-1700.23" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2039.98,-1601.58 2041.01,-1591.63 2043,-1591.84 2041.97,-1601.78 2039.98,-1601.58" />
<polyline fill="none" stroke="black" points="2039.5,-1596.5 2044.47,-1597.02 " />
<polygon fill="black" stroke="black" points="2044.95,-1602.09 2045.98,-1592.15 2047.97,-1592.35 2046.94,-1602.3 2044.95,-1602.09" />
<polyline fill="none" stroke="black" points="2044.47,-1597.02 2049.45,-1597.53 " />
<text text-anchor="start" x="2100.5" y="-1692.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_host_superordinate_domain</text>
<text text-anchor="start" x="2100.5" y="-1703.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk_host_superordinate_domain</text>
</g> <!-- host_f21b78de&#45;&gt;registrar_6e1503e3 -->
<g id="edge69" class="edge">
<title>
@@ -1839,26 +1839,26 @@ td.section {
<title>
domaindsdatahistory_995b060d:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3037.23,-801.4C3015.74,-800.86 3002.54,-797.58 2984,-778.5 2964,-757.92 2988.94,-733.73 2966,-716.5 2938.46,-695.82 2684.97,-697.87 2656,-716.5 2636.42,-729.09 2650.86,-760.46 2639.87,-770.42" />
<path fill="none" stroke="black" d="M3037.23,-801.4C3015.74,-800.86 3002.54,-797.58 2984,-778.5 2964,-757.92 2988.94,-733.73 2966,-716.5 2938.46,-695.82 2685.1,-698.07 2656,-716.5 2636.91,-728.59 2650.46,-758.9 2639.63,-768.52" />
<polygon fill="black" stroke="black" points="3045.5,-801.45 3055.48,-806 3050.5,-801.47 3055.5,-801.5 3055.5,-801.5 3055.5,-801.5 3050.5,-801.47 3055.52,-797 3045.5,-801.45 3045.5,-801.45" />
<ellipse fill="none" stroke="black" cx="3041.5" cy="-801.42" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2632.45,-777.97 2629.46,-768.43 2631.37,-767.83 2634.35,-777.38 2632.45,-777.97" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.77,-772.01 " />
<polygon fill="black" stroke="black" points="2637.22,-776.48 2634.24,-766.94 2636.14,-766.34 2639.13,-775.89 2637.22,-776.48" />
<polyline fill="none" stroke="black" points="2634.77,-772.01 2639.54,-770.52 " />
<polygon fill="black" stroke="black" points="2632.43,-775.98 2629.48,-766.43 2631.39,-765.84 2634.34,-775.39 2632.43,-775.98" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.78,-770.02 " />
<polygon fill="black" stroke="black" points="2637.21,-774.5 2634.25,-764.95 2636.16,-764.36 2639.12,-773.91 2637.21,-774.5" />
<polyline fill="none" stroke="black" points="2634.78,-770.02 2639.55,-768.54 " />
<text text-anchor="start" x="2726.5" y="-720.3" font-family="Helvetica,sans-Serif" font-size="14.00">fko4ilgyyfnvppbpuivus565i0j</text>
</g> <!-- domaindsdatahistory_995b060d&#45;&gt;domainhistory_a54cc226 -->
<g id="edge38" class="edge">
<title>
domaindsdatahistory_995b060d:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3037.23,-820.14C2938.18,-816.69 2728.91,-790.3 2656,-830.5 2641.75,-838.36 2646.61,-857.74 2639.49,-865.85" />
<polygon fill="black" stroke="black" points="3045.5,-820.3 3055.41,-825 3050.5,-820.4 3055.5,-820.5 3055.5,-820.5 3055.5,-820.5 3050.5,-820.4 3055.59,-816 3045.5,-820.3 3045.5,-820.3" />
<ellipse fill="none" stroke="black" cx="3041.5" cy="-820.22" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2632.73,-873.81 2629.14,-864.47 2631,-863.76 2634.6,-873.09 2632.73,-873.81" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.67,-867.7 " />
<polygon fill="black" stroke="black" points="2637.4,-872.01 2633.8,-862.68 2635.67,-861.96 2639.26,-871.29 2637.4,-872.01" />
<polyline fill="none" stroke="black" points="2634.67,-867.7 2639.33,-865.91 " />
<path fill="none" stroke="black" d="M3037.24,-820.15C2938.25,-816.8 2729.39,-791.2 2656,-830.5 2642.43,-837.76 2646.18,-855.59 2639.66,-863.55" />
<polygon fill="black" stroke="black" points="3045.5,-820.31 3055.41,-825 3050.5,-820.4 3055.5,-820.5 3055.5,-820.5 3055.5,-820.5 3050.5,-820.4 3055.59,-816 3045.5,-820.31 3045.5,-820.31" />
<ellipse fill="none" stroke="black" cx="3041.5" cy="-820.23" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2632.82,-871.75 2629.03,-862.49 2630.89,-861.74 2634.67,-870.99 2632.82,-871.75" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.63,-865.61 " />
<polygon fill="black" stroke="black" points="2637.45,-869.86 2633.66,-860.6 2635.52,-859.85 2639.3,-869.1 2637.45,-869.86" />
<polyline fill="none" stroke="black" points="2634.63,-865.61 2639.26,-863.72 " />
<text text-anchor="start" x="2726.5" y="-834.3" font-family="Helvetica,sans-Serif" font-size="14.00">fko4ilgyyfnvppbpuivus565i0j</text>
</g> <!-- domainhistoryhost_9f3f23ee -->
<g id="node24" class="node">
@@ -1884,26 +1884,26 @@ td.section {
<title>
domainhistoryhost_9f3f23ee:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3027.22,-593.16C2929.15,-590.01 2716.2,-566.91 2656,-621.5 2633.49,-641.91 2657.4,-745.64 2639.22,-768.88" />
<path fill="none" stroke="black" d="M3027.23,-593.17C2929.16,-590.02 2716.26,-566.97 2656,-621.5 2608.67,-664.32 2687.71,-759.84 2640.02,-770.52" />
<polygon fill="black" stroke="black" points="3035.5,-593.32 3045.42,-598 3040.5,-593.41 3045.5,-593.5 3045.5,-593.5 3045.5,-593.5 3040.5,-593.41 3045.58,-589 3035.5,-593.32 3035.5,-593.32" />
<ellipse fill="none" stroke="black" cx="3031.5" cy="-593.24" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2633.14,-777.52 2628.65,-768.58 2630.44,-767.69 2634.92,-776.62 2633.14,-777.52" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.47,-771.26 " />
<polygon fill="black" stroke="black" points="2637.61,-775.28 2633.12,-766.34 2634.91,-765.44 2639.39,-774.38 2637.61,-775.28" />
<polyline fill="none" stroke="black" points="2634.47,-771.26 2638.94,-769.02 " />
<polygon fill="black" stroke="black" points="2631.48,-776.38 2630.51,-766.43 2632.5,-766.23 2633.47,-776.19 2631.48,-776.38" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.98,-771.01 " />
<polygon fill="black" stroke="black" points="2636.46,-775.89 2635.49,-765.94 2637.48,-765.75 2638.45,-775.7 2636.46,-775.89" />
<polyline fill="none" stroke="black" points="2634.98,-771.01 2639.95,-770.53 " />
<text text-anchor="start" x="2716.5" y="-625.3" font-family="Helvetica,sans-Serif" font-size="14.00">fka9woh3hu8gx5x0vly6bai327n</text>
</g> <!-- domainhistoryhost_9f3f23ee&#45;&gt;domainhistory_a54cc226 -->
<g id="edge40" class="edge">
<title>
domainhistoryhost_9f3f23ee:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3027.48,-634.52C3008.25,-639.5 2999.57,-653.53 2984,-674.5 2968.16,-695.83 2986.94,-715.15 2966,-731.5 2911.61,-773.98 2708.41,-705.59 2656,-750.5 2617.94,-783.11 2674.47,-857.33 2640.11,-868.17" />
<path fill="none" stroke="black" d="M3027.48,-634.52C3008.25,-639.5 2999.57,-653.53 2984,-674.5 2968.16,-695.83 2986.94,-715.15 2966,-731.5 2911.61,-773.98 2708.49,-705.69 2656,-750.5 2618.49,-782.52 2673.8,-855.53 2639.95,-866.19" />
<polygon fill="black" stroke="black" points="3035.56,-633.61 3046,-636.97 3040.53,-633.06 3045.5,-632.5 3045.5,-632.5 3045.5,-632.5 3040.53,-633.06 3045,-628.03 3035.56,-633.61 3035.56,-633.61" />
<ellipse fill="none" stroke="black" cx="3031.59" cy="-634.06" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2631.64,-874.33 2630.34,-864.41 2632.32,-864.15 2633.63,-874.07 2631.64,-874.33" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.96,-868.85 " />
<polygon fill="black" stroke="black" points="2636.6,-873.67 2635.3,-863.76 2637.28,-863.5 2638.58,-873.41 2636.6,-873.67" />
<polyline fill="none" stroke="black" points="2634.96,-868.85 2639.91,-868.2 " />
<polygon fill="black" stroke="black" points="2631.64,-872.33 2630.34,-862.41 2632.32,-862.15 2633.63,-872.07 2631.64,-872.33" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.96,-866.85 " />
<polygon fill="black" stroke="black" points="2636.6,-871.68 2635.3,-861.76 2637.28,-861.5 2638.58,-871.41 2636.6,-871.68" />
<polyline fill="none" stroke="black" points="2634.96,-866.85 2639.91,-866.2 " />
<text text-anchor="start" x="2716.5" y="-754.3" font-family="Helvetica,sans-Serif" font-size="14.00">fka9woh3hu8gx5x0vly6bai327n</text>
</g> <!-- domaintransactionrecord_6e77ff61 -->
<g id="node25" class="node">
@@ -1934,33 +1934,33 @@ td.section {
<title>
domaintransactionrecord_6e77ff61:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3005.2,-969.73C2995.87,-961.16 2994.42,-944.37 2984,-928.5 2974.4,-913.87 2980.58,-903.16 2966,-893.5 2908.41,-855.36 2709.21,-914.55 2656,-870.5 2624.84,-844.71 2665.46,-786 2640.13,-775.22" />
<path fill="none" stroke="black" d="M3005.2,-969.73C2995.87,-961.16 2994.42,-944.37 2984,-928.5 2974.4,-913.87 2980.58,-903.16 2966,-893.5 2908.41,-855.36 2709.1,-914.68 2656,-870.5 2624.17,-844.02 2666.43,-783.73 2639.98,-773.11" />
<polygon fill="black" stroke="black" points="3013.01,-972.34 3021.08,-979.77 3017.76,-973.92 3022.5,-975.5 3022.5,-975.5 3022.5,-975.5 3017.76,-973.92 3023.92,-971.23 3013.01,-972.34 3013.01,-972.34" />
<ellipse fill="none" stroke="black" cx="3009.22" cy="-971.07" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2630.15,-778.6 2631.82,-768.74 2633.79,-769.07 2632.12,-778.93 2630.15,-778.6" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.93,-774.34 " />
<polygon fill="black" stroke="black" points="2635.08,-779.43 2636.75,-769.57 2638.72,-769.91 2637.05,-779.77 2635.08,-779.43" />
<polyline fill="none" stroke="black" points="2634.93,-774.34 2639.86,-775.17 " />
<polygon fill="black" stroke="black" points="2630.19,-776.6 2631.78,-766.72 2633.76,-767.04 2632.16,-776.91 2630.19,-776.6" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.94,-772.3 " />
<polygon fill="black" stroke="black" points="2635.13,-777.39 2636.72,-767.52 2638.7,-767.84 2637.1,-777.71 2635.13,-777.39" />
<polyline fill="none" stroke="black" points="2634.94,-772.3 2639.87,-773.1 " />
<text text-anchor="start" x="2721" y="-897.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkcjqe54u72kha71vkibvxhjye7</text>
</g> <!-- domaintransactionrecord_6e77ff61&#45;&gt;domainhistory_a54cc226 -->
<g id="edge42" class="edge">
<title>
domaintransactionrecord_6e77ff61:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3004.86,-960.38C2992,-966.91 2986.04,-979.81 2966,-984.5 2932.46,-992.35 2682.91,-1006 2656,-984.5 2618.26,-954.34 2672.54,-882.05 2640.2,-870.94" />
<path fill="none" stroke="black" d="M3004.86,-960.38C2992,-966.91 2986.04,-979.81 2966,-984.5 2932.46,-992.35 2682.87,-1006.05 2656,-984.5 2617.53,-953.64 2673.66,-879.64 2639.94,-868.83" />
<polygon fill="black" stroke="black" points="3012.73,-958.65 3023.47,-960.89 3017.62,-957.57 3022.5,-956.5 3022.5,-956.5 3022.5,-956.5 3017.62,-957.57 3021.53,-952.11 3012.73,-958.65 3012.73,-958.65" />
<ellipse fill="none" stroke="black" cx="3008.83" cy="-959.51" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2630.29,-874.59 2631.69,-864.69 2633.67,-864.97 2632.27,-874.87 2630.29,-874.59" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.95,-870.2 " />
<polygon fill="black" stroke="black" points="2635.24,-875.29 2636.64,-865.39 2638.62,-865.67 2637.22,-875.57 2635.24,-875.29" />
<polyline fill="none" stroke="black" points="2634.95,-870.2 2639.9,-870.9 " />
<polygon fill="black" stroke="black" points="2630.33,-872.59 2631.65,-862.68 2633.64,-862.94 2632.31,-872.85 2630.33,-872.59" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.96,-868.16 " />
<polygon fill="black" stroke="black" points="2635.28,-873.25 2636.61,-863.34 2638.59,-863.6 2637.27,-873.52 2635.28,-873.25" />
<polyline fill="none" stroke="black" points="2634.96,-868.16 2639.91,-868.82 " />
<text text-anchor="start" x="2721" y="-1000.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkcjqe54u72kha71vkibvxhjye7</text>
</g> <!-- domaintransactionrecord_6e77ff61&#45;&gt;tld_f1fa57e2 -->
<g id="edge84" class="edge">
<title>
domaintransactionrecord_6e77ff61:w-&gt;tld_f1fa57e2:e
</title>
<path fill="none" stroke="black" d="M3005,-999.82C2987.94,-1013.55 2994.77,-1050.11 2966,-1064.5 2877.7,-1108.67 2176.49,-1089.6 2078,-1096.5 1898.36,-1109.08 1854.13,-1119.97 1675,-1138.5 1518.23,-1154.71 1477.31,-1174.62 1324.71,-1175.47" />
<path fill="none" stroke="black" d="M3005,-999.82C2987.94,-1013.55 2994.77,-1050.11 2966,-1064.5 2877.7,-1108.67 2176.48,-1089.48 2078,-1096.5 1898.33,-1109.31 1854.17,-1120.99 1675,-1139.5 1518.27,-1155.69 1477.26,-1174.67 1324.71,-1175.47" />
<polygon fill="black" stroke="black" points="3012.93,-997.41 3023.81,-998.81 3017.72,-995.95 3022.5,-994.5 3022.5,-994.5 3022.5,-994.5 3017.72,-995.95 3021.19,-990.19 3012.93,-997.41 3012.93,-997.41" />
<ellipse fill="none" stroke="black" cx="3009.11" cy="-998.57" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="1315.51,-1180.5 1315.49,-1170.5 1317.49,-1170.49 1317.51,-1180.49 1315.51,-1180.5" />
@@ -1992,27 +1992,27 @@ td.section {
<title>
graceperiodhistory_40ccc1f1:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3025.59,-714.1C3003.62,-707.57 2995.8,-689.58 2966,-683.5 2831,-655.96 2766.12,-600.69 2656,-683.5 2626.1,-705.98 2662.55,-760.71 2640.06,-771.6" />
<path fill="none" stroke="black" d="M3025.59,-714.1C3003.62,-707.57 2995.8,-689.58 2966,-683.5 2831,-655.96 2766.37,-601.03 2656,-683.5 2626.65,-705.43 2661.97,-758.99 2639.87,-769.64" />
<polygon fill="black" stroke="black" points="3033.59,-715.17 3042.9,-720.96 3038.54,-715.83 3043.5,-716.5 3043.5,-716.5 3043.5,-716.5 3038.54,-715.83 3044.1,-712.04 3033.59,-715.17 3033.59,-715.17" />
<ellipse fill="none" stroke="black" cx="3029.62" cy="-714.64" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2631.91,-778.23 2630.05,-768.4 2632.02,-768.03 2633.88,-777.86 2631.91,-778.23" />
<polyline fill="none" stroke="black" points="2630,-773.5 2634.91,-772.57 " />
<polygon fill="black" stroke="black" points="2636.82,-777.3 2634.97,-767.47 2636.93,-767.1 2638.79,-776.93 2636.82,-777.3" />
<polyline fill="none" stroke="black" points="2634.91,-772.57 2639.83,-771.64 " />
<polygon fill="black" stroke="black" points="2631.91,-776.23 2630.06,-766.4 2632.02,-766.03 2633.87,-775.86 2631.91,-776.23" />
<polyline fill="none" stroke="black" points="2630,-771.5 2634.91,-770.57 " />
<polygon fill="black" stroke="black" points="2636.82,-775.3 2634.97,-765.48 2636.94,-765.11 2638.79,-774.93 2636.82,-775.3" />
<polyline fill="none" stroke="black" points="2634.91,-770.57 2639.83,-769.65 " />
<text text-anchor="start" x="2717" y="-687.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk7w3cx8d55q8bln80e716tr7b8</text>
</g> <!-- graceperiodhistory_40ccc1f1&#45;&gt;domainhistory_a54cc226 -->
<g id="edge44" class="edge">
<title>
graceperiodhistory_40ccc1f1:w-&gt;domainhistory_a54cc226:e
</title>
<path fill="none" stroke="black" d="M3025.76,-700.91C2999.06,-712.97 3002.6,-753.74 2966,-769.5 2902.59,-796.8 2710.51,-747.15 2656,-789.5 2629.82,-809.84 2659.02,-857.01 2639.91,-867.44" />
<polygon fill="black" stroke="black" points="3033.68,-699.39 3044.35,-701.92 3038.59,-698.44 3043.5,-697.5 3043.5,-697.5 3043.5,-697.5 3038.59,-698.44 3042.65,-693.08 3033.68,-699.39 3033.68,-699.39" />
<path fill="none" stroke="black" d="M3025.76,-700.9C2999.05,-712.95 3002.58,-753.68 2966,-769.5 2902.62,-796.9 2710.7,-748.36 2656,-790.5 2630.74,-809.96 2657.97,-855.07 2639.84,-865.38" />
<polygon fill="black" stroke="black" points="3033.68,-699.38 3044.35,-701.92 3038.59,-698.44 3043.5,-697.5 3043.5,-697.5 3043.5,-697.5 3038.59,-698.44 3042.65,-693.08 3033.68,-699.38 3033.68,-699.38" />
<ellipse fill="none" stroke="black" cx="3029.75" cy="-700.14" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2632,-874.19 2629.96,-864.4 2631.92,-863.99 2633.96,-873.78 2632,-874.19" />
<polyline fill="none" stroke="black" points="2630,-869.5 2634.89,-868.48 " />
<polygon fill="black" stroke="black" points="2636.89,-873.17 2634.85,-863.38 2636.81,-862.97 2638.85,-872.76 2636.89,-873.17" />
<polyline fill="none" stroke="black" points="2634.89,-868.48 2639.79,-867.46 " />
<text text-anchor="start" x="2717" y="-793.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk7w3cx8d55q8bln80e716tr7b8</text>
<polygon fill="black" stroke="black" points="2632.03,-872.18 2629.92,-862.4 2631.88,-861.98 2633.99,-871.76 2632.03,-872.18" />
<polyline fill="none" stroke="black" points="2630,-867.5 2634.89,-866.45 " />
<polygon fill="black" stroke="black" points="2636.92,-871.12 2634.81,-861.35 2636.77,-860.93 2638.87,-870.7 2636.92,-871.12" />
<polyline fill="none" stroke="black" points="2634.89,-866.45 2639.78,-865.39 " />
<text text-anchor="start" x="2717" y="-794.3" font-family="Helvetica,sans-Serif" font-size="14.00">fk7w3cx8d55q8bln80e716tr7b8</text>
</g> <!-- featureflag_3ee43a78 -->
<g id="node27" class="node">
<title>
@@ -2157,28 +2157,25 @@ td.section {
<title>
registrarpoc_ab47054d
</title>
<polygon fill="#e9c2f2" stroke="transparent" points="499.5,-459.5 499.5,-478.5 645.5,-478.5 645.5,-459.5 499.5,-459.5" />
<text text-anchor="start" x="501.5" y="-466.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."RegistrarPoc"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="645.5,-459.5 645.5,-478.5 719.5,-478.5 719.5,-459.5 645.5,-459.5" />
<text text-anchor="start" x="680.5" y="-465.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="501.5" y="-447.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="634.5" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="647.5" y="-446.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="501.5" y="-428.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">registrar_id</text>
<text text-anchor="start" x="634.5" y="-427.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="647.5" y="-427.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="501.5" y="-408.3" font-family="Helvetica,sans-Serif" font-size="14.00">login_email_address</text>
<text text-anchor="start" x="634.5" y="-408.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="647.5" y="-408.3" font-family="Helvetica,sans-Serif" font-size="14.00">text</text>
<polygon fill="none" stroke="#888888" points="498.5,-401.5 498.5,-479.5 720.5,-479.5 720.5,-401.5 498.5,-401.5" />
<polygon fill="#e9c2f2" stroke="transparent" points="499.5,-460.5 499.5,-479.5 645.5,-479.5 645.5,-460.5 499.5,-460.5" />
<text text-anchor="start" x="501.5" y="-467.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."RegistrarPoc"</text>
<polygon fill="#e9c2f2" stroke="transparent" points="645.5,-460.5 645.5,-479.5 719.5,-479.5 719.5,-460.5 645.5,-460.5" />
<text text-anchor="start" x="680.5" y="-466.3" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text>
<text text-anchor="start" x="501.5" y="-448.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">email_address</text>
<text text-anchor="start" x="619.5" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="647.5" y="-447.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<text text-anchor="start" x="501.5" y="-429.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">registrar_id</text>
<text text-anchor="start" x="619.5" y="-428.3" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
<text text-anchor="start" x="647.5" y="-428.3" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text>
<polygon fill="none" stroke="#888888" points="498.5,-422 498.5,-481 720.5,-481 720.5,-422 498.5,-422" />
</g> <!-- registrarpoc_ab47054d&#45;&gt;registrar_6e1503e3 -->
<g id="edge76" class="edge">
<title>
registrarpoc_ab47054d:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M480.42,-430.22C370.26,-426.89 305.01,-396.32 231,-484.5 196.2,-525.96 256.02,-1414.99 212.97,-1501.71" />
<polygon fill="black" stroke="black" points="488.5,-430.35 498.43,-435 493.5,-430.42 498.5,-430.5 498.5,-430.5 498.5,-430.5 493.5,-430.42 498.57,-426 488.5,-430.35 488.5,-430.35" />
<ellipse fill="none" stroke="black" cx="484.5" cy="-430.28" rx="4" ry="4" />
<path fill="none" stroke="black" d="M480.44,-431.22C370.35,-427.81 305.12,-396.51 231,-484.5 196.13,-525.9 256.01,-1414.98 212.97,-1501.71" />
<polygon fill="black" stroke="black" points="488.5,-431.34 498.43,-436 493.5,-431.42 498.5,-431.5 498.5,-431.5 498.5,-431.5 493.5,-431.42 498.57,-427 488.5,-431.34 488.5,-431.34" />
<ellipse fill="none" stroke="black" cx="484.5" cy="-431.28" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="209.6,-1511.53 202.88,-1504.13 204.36,-1502.78 211.08,-1510.18 209.6,-1511.53" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 209.2,-1505.14 " />
<polygon fill="black" stroke="black" points="213.3,-1508.16 206.58,-1500.76 208.06,-1499.42 214.78,-1506.82 213.3,-1508.16" />
@@ -2208,27 +2205,27 @@ td.section {
<title>
registrarupdatehistory_8a38bed4:w-&gt;registrar_6e1503e3:e
</title>
<path fill="none" stroke="black" d="M1075.03,-316.53C1068.62,-325.21 1066.22,-338.59 1052,-344.5 928.1,-395.95 582.09,-356.67 449,-373.5 350.85,-385.91 294.38,-342.53 231,-418.5 194.01,-462.83 259.39,-1412.86 213.16,-1501.83" />
<polygon fill="black" stroke="black" points="1082.51,-312.88 1093.47,-312.55 1087.01,-310.69 1091.5,-308.5 1091.5,-308.5 1091.5,-308.5 1087.01,-310.69 1089.53,-304.45 1082.51,-312.88 1082.51,-312.88" />
<ellipse fill="none" stroke="black" cx="1078.92" cy="-314.63" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="209.54,-1511.61 202.97,-1504.07 204.48,-1502.76 211.05,-1510.3 209.54,-1511.61" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 209.27,-1505.22 " />
<polygon fill="black" stroke="black" points="213.31,-1508.33 206.74,-1500.79 208.25,-1499.48 214.82,-1507.02 213.31,-1508.33" />
<polyline fill="none" stroke="black" points="209.27,-1505.22 213.04,-1501.93 " />
<text text-anchor="start" x="508.5" y="-377.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarupdatehistoryregistrarid</text>
<path fill="none" stroke="black" d="M1075.29,-316.72C1068.17,-327.97 1069.11,-347.77 1052,-356.5 932.41,-417.51 582.71,-381.43 449,-393.5 351.82,-402.27 296.63,-347.3 231,-419.5 192.2,-462.18 259.17,-1412.8 213.14,-1501.83" />
<polygon fill="black" stroke="black" points="1082.58,-313.02 1093.54,-312.51 1087.04,-310.76 1091.5,-308.5 1091.5,-308.5 1091.5,-308.5 1087.04,-310.76 1089.46,-304.49 1082.58,-313.02 1082.58,-313.02" />
<ellipse fill="none" stroke="black" cx="1079.01" cy="-314.83" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="209.54,-1511.61 202.96,-1504.08 204.47,-1502.76 211.05,-1510.29 209.54,-1511.61" />
<polyline fill="none" stroke="black" points="205.5,-1508.5 209.27,-1505.21 " />
<polygon fill="black" stroke="black" points="213.31,-1508.32 206.73,-1500.79 208.24,-1499.47 214.81,-1507 213.31,-1508.32" />
<polyline fill="none" stroke="black" points="209.27,-1505.21 213.03,-1501.92 " />
<text text-anchor="start" x="508.5" y="-397.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarupdatehistoryregistrarid</text>
</g> <!-- registrarupdatehistory_8a38bed4&#45;&gt;user_f2216f01 -->
<g id="edge87" class="edge">
<title>
registrarupdatehistory_8a38bed4:w-&gt;user_f2216f01:e
</title>
<path fill="none" stroke="black" d="M1073.16,-288.05C1007.37,-280.62 817.84,-246.96 788,-241.5 752.99,-235.09 742.65,-225.52 711.79,-223.78" />
<polygon fill="black" stroke="black" points="1081.53,-288.71 1091.15,-293.99 1086.52,-289.11 1091.5,-289.5 1091.5,-289.5 1091.5,-289.5 1086.52,-289.11 1091.85,-285.01 1081.53,-288.71 1081.53,-288.71" />
<ellipse fill="none" stroke="black" cx="1077.54" cy="-288.4" rx="4" ry="4" />
<path fill="none" stroke="black" d="M1073.33,-289.35C951.73,-287.35 916.17,-264.96 788,-241.5 752.99,-235.09 742.65,-225.52 711.79,-223.78" />
<polygon fill="black" stroke="black" points="1081.5,-289.42 1091.46,-294 1086.5,-289.46 1091.5,-289.5 1091.5,-289.5 1091.5,-289.5 1086.5,-289.46 1091.54,-285 1081.5,-289.42 1081.5,-289.42" />
<ellipse fill="none" stroke="black" cx="1077.5" cy="-289.39" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="702.37,-228.53 702.63,-218.53 704.63,-218.58 704.36,-228.58 702.37,-228.53" />
<polyline fill="none" stroke="black" points="701.5,-223.5 706.5,-223.63 " />
<polygon fill="black" stroke="black" points="707.36,-228.66 707.63,-218.66 709.63,-218.72 709.36,-228.71 707.36,-228.66" />
<polyline fill="none" stroke="black" points="706.5,-223.63 711.5,-223.77 " />
<text text-anchor="start" x="829.5" y="-289.3" font-family="Helvetica,sans-Serif" font-size="14.00">fksr7w342s7x5s5jvdti2axqeq8</text>
<text text-anchor="start" x="829.5" y="-293.3" font-family="Helvetica,sans-Serif" font-size="14.00">fksr7w342s7x5s5jvdti2axqeq8</text>
</g> <!-- registrarpocupdatehistory_31e5d9aa -->
<g id="node37" class="node">
<title>
@@ -2269,27 +2266,27 @@ td.section {
<title>
registrarpocupdatehistory_31e5d9aa:w-&gt;registrarpoc_ab47054d:e
</title>
<path fill="none" stroke="black" d="M1060.25,-412.4C945.91,-411.1 909.85,-398.18 788,-413.5 761.02,-416.89 753.22,-427.45 730.72,-429.96" />
<path fill="none" stroke="black" d="M1060.25,-412.39C945.9,-411.02 909.74,-397.33 788,-413.5 760.95,-417.09 753.29,-428.27 730.75,-430.93" />
<polygon fill="black" stroke="black" points="1068.5,-412.44 1078.47,-417 1073.5,-412.47 1078.5,-412.5 1078.5,-412.5 1078.5,-412.5 1073.5,-412.47 1078.53,-408 1068.5,-412.44 1068.5,-412.44" />
<ellipse fill="none" stroke="black" cx="1064.5" cy="-412.42" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="721.76,-435.44 721.24,-425.45 723.23,-425.35 723.76,-435.34 721.76,-435.44" />
<polyline fill="none" stroke="black" points="720.5,-430.5 725.49,-430.24 " />
<polygon fill="black" stroke="black" points="726.75,-435.18 726.23,-425.19 728.23,-425.09 728.75,-435.07 726.75,-435.18" />
<polyline fill="none" stroke="black" points="725.49,-430.24 730.49,-429.97 " />
<polygon fill="black" stroke="black" points="721.78,-436.44 721.22,-426.45 723.22,-426.34 723.77,-436.33 721.78,-436.44" />
<polyline fill="none" stroke="black" points="720.5,-431.5 725.49,-431.22 " />
<polygon fill="black" stroke="black" points="726.77,-436.16 726.21,-426.17 728.21,-426.06 728.77,-436.05 726.77,-436.16" />
<polyline fill="none" stroke="black" points="725.49,-431.22 730.48,-430.94 " />
<text text-anchor="start" x="798" y="-417.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkregistrarpocupdatehistoryemailaddress</text>
</g> <!-- registrarpocupdatehistory_31e5d9aa&#45;&gt;user_f2216f01 -->
<g id="edge86" class="edge">
<title>
registrarpocupdatehistory_31e5d9aa:w-&gt;user_f2216f01:e
</title>
<path fill="none" stroke="black" d="M1060.51,-389.56C998,-372.91 800.77,-308.81 788,-301.5 746.24,-277.58 752.11,-230.72 711.54,-224.25" />
<polygon fill="black" stroke="black" points="1068.73,-391.36 1077.54,-397.9 1073.62,-392.43 1078.5,-393.5 1078.5,-393.5 1078.5,-393.5 1073.62,-392.43 1079.46,-389.1 1068.73,-391.36 1068.73,-391.36" />
<ellipse fill="none" stroke="black" cx="1064.82" cy="-390.51" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="702.13,-228.56 702.87,-218.59 704.86,-218.74 704.12,-228.71 702.13,-228.56" />
<polyline fill="none" stroke="black" points="701.5,-223.5 706.49,-223.87 " />
<polygon fill="black" stroke="black" points="707.11,-228.93 707.85,-218.96 709.85,-219.11 709.11,-229.08 707.11,-228.93" />
<polyline fill="none" stroke="black" points="706.49,-223.87 711.47,-224.24 " />
<text text-anchor="start" x="835.5" y="-390.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkftpbwctxtkc1i0njc0tdcaa2g</text>
<path fill="none" stroke="black" d="M1060.63,-389.15C998.08,-372.3 794.23,-312.16 788,-308.5 744.69,-283.09 753.78,-231.25 711.64,-224.28" />
<polygon fill="black" stroke="black" points="1068.78,-391.13 1077.43,-397.87 1073.64,-392.32 1078.5,-393.5 1078.5,-393.5 1078.5,-393.5 1073.64,-392.32 1079.57,-389.13 1068.78,-391.13 1068.78,-391.13" />
<ellipse fill="none" stroke="black" cx="1064.9" cy="-390.19" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="702.11,-228.56 702.88,-218.59 704.88,-218.75 704.11,-228.72 702.11,-228.56" />
<polyline fill="none" stroke="black" points="701.5,-223.5 706.49,-223.88 " />
<polygon fill="black" stroke="black" points="707.1,-228.95 707.87,-218.98 709.86,-219.13 709.09,-229.1 707.1,-228.95" />
<polyline fill="none" stroke="black" points="706.49,-223.88 711.47,-224.27 " />
<text text-anchor="start" x="835.5" y="-391.3" font-family="Helvetica,sans-Serif" font-size="14.00">fkftpbwctxtkc1i0njc0tdcaa2g</text>
</g> <!-- registrylock_ac88663e -->
<g id="node38" class="node">
<title>
@@ -6145,11 +6142,6 @@ td.section {
<td class="minwidth"><b><i>registrar_id</i></b></td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">login_email_address</td>
<td class="minwidth">text</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
File diff suppressed because it is too large Load Diff
+3
View File
@@ -173,3 +173,6 @@ V172__allocation_token_renewal_price.sql
V173__create_feature_flag_table.sql
V174__user_pkey.sql
V175__user_update_history_id.sql
V176__drop_login_email_address_column_from_registrar_poc.sql
V177__drop_user_id.sql
V178__drop_user_id_history.sql
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- 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.
alter table "RegistrarPoc" drop column login_email_address;
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- 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.
ALTER TABLE "User" DROP COLUMN id;
@@ -0,0 +1,15 @@
-- Copyright 2024 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- 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.
ALTER TABLE "UserUpdateHistory" DROP COLUMN user_id;
@@ -893,7 +893,6 @@
global_role text not null,
is_admin boolean not null,
registrar_roles hstore,
id int8,
primary key (email_address)
);
@@ -905,7 +904,6 @@
history_type text not null,
history_url text not null,
email_address text not null,
user_id int8 not null,
registry_lock_email_address text,
registry_lock_password_hash text,
registry_lock_password_salt text,

Some files were not shown because too many files have changed in this diff Show More