Compare commits

..
Author SHA1 Message Date
Weimin YuandGitHub 19819444af Set upper bound of netty version (#2482)
A new alpha version is introduced and breaks our tests.
2024-06-17 19:43:36 +00:00
Pavlo TkachandGitHub 15df3aea44 Update Angular 17 -> 18 (#2479) 2024-06-14 23:09:34 +00:00
Weimin YuandGitHub d000a5dff8 Use replica db for non-mutating epp flows (#2478)
* Use replica db for non-mutating epp flows

* Add a test
2024-06-13 23:18:56 +00:00
sarahcaseybotandGitHub 34694b4aef Add the FeatureFlag entity (#2464)
* Add FeatureFlag entity

* Add converter

* Add loading cache

* Add more tests

* Fix NPE in cache

* small fixes
2024-06-12 16:44:08 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7ce7b23450 Bump braces (#2476)
Bumps the npm_and_yarn group with 1 update in the /console-webapp directory: [braces](https://github.com/micromatch/braces).


Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  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-06-11 19:19:16 +00:00
Lai JiangandGitHub a5d1469281 Upgrade to Gradle 8.8 (#2475) 2024-06-10 14:56:10 +00:00
Pavlo TkachandGitHub a90a85afae Fix domain page "not found" layout issue (#2474) 2024-06-08 11:36:23 +00:00
Weimin YuandGitHub 6e68876a14 Use replica for whois/rdap (#2470) 2024-06-07 17:03:55 +00:00
Lai JiangandGitHub 11231703d5 Upgrade to jakarta mail (#2473) 2024-06-07 15:28:36 +00:00
gbrodmanandGitHub b77a219e19 Move domain-list search+download outside of loading bar (#2457)
This means that they'll stick around even while we're loading domains
from the server.

https://b.corp.google.com/issues/343213150
2024-06-06 20:35:20 +00:00
Pavlo TkachandGitHub bd8e6354b5 Add new registrar screen to the console (#2469) 2024-06-07 00:21:53 +00:00
Weimin YuandGitHub 361094f537 BSA check in DomainCheckFlow should check TLD (#2472)
Should not block labels if the tld is not enrolled with BSA.
2024-06-06 19:30:36 +00:00
sarahcaseybotandGitHub d53177e44c Add domain creates to the load testing client (#2458)
* Add domain creates to the load testing client

* Update contact create
2024-06-06 17:30:12 +00:00
sarahcaseybotandGitHub e73f646e1f Add FeatureFlag table to the database (#2463)
* Add FeatureFlag table to the database

* Change status to hstore
2024-06-06 17:17:11 +00:00
Lai JiangandGitHub 1a5dfb0ac2 Upgrade schemacrawler (#2471) 2024-06-06 14:51:13 +00:00
Lai JiangandGitHub 49cb1875d1 Upgrade dependencies (#2468) 2024-06-05 15:50:42 +00:00
gbrodmanandGitHub 61eee45ad0 Add renewalPrice fields to AllocationToken in SQL (#2462)
This is an optional field (will be required when the renewal price
behavior is SPECIFIED). This will allow us to set arbitrary renewal
prices for domains as part of one-off negotiations.

https://b.corp.google.com/issues/332928676
2024-06-03 19:50:58 +00:00
Weimin YuandGitHub e99a18f54f Pass log trace_id to TimeLimiter task (#2466)
Code executed by TimeLimiter is in another thread. Pass on the log
trace_id if exists.
2024-06-03 19:38:17 +00:00
138 changed files with 6442 additions and 4405 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ com.google.guava:failureaccess:1.0.2=compileClasspath,deploy_jar,runtimeClasspat
com.google.guava:guava-parent:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.guava:guava:31.0.1-jre=checkstyle
com.google.guava:guava:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.guava:guava:33.2.0-jre=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:guava:33.2.1-jre=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
com.google.j2objc:j2objc-annotations:1.3=checkstyle
+10 -7
View File
@@ -15,12 +15,16 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/console-webapp",
"outputPath": {
"base": "staged/dist/",
"browser": ""
},
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
@@ -34,7 +38,8 @@
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": []
"scripts": [],
"browser": "src/main.ts"
},
"configurations": {
"production": {
@@ -59,9 +64,7 @@
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
+2280 -1631
View File
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -4,7 +4,7 @@
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config dev-proxy.config.json",
"build": "ng build --base-href=/console/ --output-path=staged/dist",
"build": "ng build --base-href=/console/",
"build:local": "ng build --base-href=/default/console/",
"watch": "ng build --watch --configuration development",
"test": "ng test --browsers=ChromeHeadless --watch=false",
@@ -16,29 +16,29 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.5",
"@angular/cdk": "^17.3.5",
"@angular/common": "^17.3.5",
"@angular/compiler": "^17.3.5",
"@angular/core": "^17.3.5",
"@angular/forms": "^17.3.5",
"@angular/material": "^17.3.5",
"@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5",
"@angular/router": "^17.3.5",
"@angular/animations": "^18.0.2",
"@angular/cdk": "^18.0.2",
"@angular/common": "^18.0.2",
"@angular/compiler": "^18.0.2",
"@angular/core": "^18.0.2",
"@angular/forms": "^18.0.2",
"@angular/material": "^18.0.2",
"@angular/platform-browser": "^18.0.2",
"@angular/platform-browser-dynamic": "^18.0.2",
"@angular/router": "^18.0.2",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.5",
"@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "~17.3.5",
"@angular/compiler-cli": "^17.3.5",
"@angular-devkit/build-angular": "^18.0.3",
"@angular-eslint/builder": "18.0.1",
"@angular-eslint/eslint-plugin": "18.0.1",
"@angular-eslint/eslint-plugin-template": "18.0.1",
"@angular-eslint/schematics": "18.0.1",
"@angular-eslint/template-parser": "18.0.1",
"@angular/cli": "~18.0.3",
"@angular/compiler-cli": "^18.0.2",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^7.2.0",
@@ -52,6 +52,6 @@
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"prettier": "2.8.7",
"typescript": "~5.2.2"
"typescript": "~5.4.5"
}
}
}
+9 -9
View File
@@ -12,25 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './material.module';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { BackendService } from './shared/services/backend.service';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
RouterTestingModule,
MaterialModule,
BrowserAnimationsModule,
],
providers: [BackendService],
declarations: [AppComponent],
imports: [RouterTestingModule, MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
});
+5 -3
View File
@@ -23,13 +23,14 @@ import { MaterialModule } from './material.module';
import { BackendService } from './shared/services/backend.service';
import { HttpClientModule } from '@angular/common/http';
import { provideHttpClient } from '@angular/common/http';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import { DomainListComponent } from './domains/domainList.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { NavigationComponent } from './navigation/navigation.component';
import NewRegistrarComponent from './registrar/newRegistrar.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
import { RegistrarComponent } from './registrar/registrarsTable.component';
@@ -63,6 +64,7 @@ import { TldsComponent } from './tlds/tlds.component';
HomeComponent,
LocationBackDirective,
NavigationComponent,
NewRegistrarComponent,
NotificationsComponent,
RegistrarComponent,
RegistrarDetailsComponent,
@@ -78,12 +80,12 @@ import { TldsComponent } from './tlds/tlds.component';
WhoisComponent,
WhoisEditComponent,
],
bootstrap: [AppComponent],
imports: [
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
FormsModule,
HttpClientModule,
MaterialModule,
SnackBarModule,
],
@@ -98,7 +100,7 @@ import { TldsComponent } from './tlds/tlds.component';
subscriptSizing: 'dynamic',
},
},
provideHttpClient(),
],
bootstrap: [AppComponent],
})
export class AppModule {}
@@ -1,7 +1,8 @@
<app-selected-registrar-wrapper>
<div class="console-domains">
<div class="console-app-domains">
<h1 class="mat-headline-4">Domains</h1>
@if (totalResults === 0) {
@if (!isLoading && totalResults == 0) {
<div class="console-app__empty-domains">
<h1>
<mat-icon class="console-app__empty-domains-icon secondary-text"
@@ -10,76 +11,85 @@
</h1>
<h1>No domains found</h1>
</div>
} @else if(isLoading) {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
} @else {
<a
mat-stroked-button
color="primary"
href="/console-api/dum-download?registrarId={{
registrarService.registrarId()
}}"
class="console-app-domains__download"
>
<mat-icon>download</mat-icon>
Download domains (.csv)
</a>
<mat-form-field class="console-app__domains-filter">
<mat-label>Filter</mat-label>
<input
type="search"
matInput
[(ngModel)]="searchTerm"
(ngModelChange)="sendInput()"
#input
/>
</mat-form-field>
<mat-table
[dataSource]="dataSource"
class="mat-elevation-z0"
class="console-app__domains-table"
>
<ng-container matColumnDef="domainName">
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.domainName }}</mat-cell>
</ng-container>
<div class="console-app__domains-table-parent">
@if (isLoading) {
<div class="console-app__domains-spinner">
<mat-spinner />
</div>
} @else {
<a
mat-stroked-button
color="primary"
href="/console-api/dum-download?registrarId={{
registrarService.registrarId()
}}"
class="console-app-domains__download"
>
<mat-icon>download</mat-icon>
Download domains (.csv)
</a>
<mat-form-field class="console-app__domains-filter">
<mat-label>Filter</mat-label>
<input
type="search"
matInput
[(ngModel)]="searchTerm"
(ngModelChange)="sendInput()"
[disabled]="isLoading"
#input
/>
</mat-form-field>
}
<mat-table
[dataSource]="dataSource"
class="mat-elevation-z0"
class="console-app__domains-table"
>
<ng-container matColumnDef="domainName">
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{
element.domainName
}}</mat-cell>
</ng-container>
<ng-container matColumnDef="creationTime">
<mat-header-cell *matHeaderCellDef>Creation Time</mat-header-cell>
<mat-cell *matCellDef="let element">
{{ element.creationTime.creationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="creationTime">
<mat-header-cell *matHeaderCellDef>Creation Time</mat-header-cell>
<mat-cell *matCellDef="let element">
{{ element.creationTime.creationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="registrationExpirationTime">
<mat-header-cell *matHeaderCellDef>Expiration Time</mat-header-cell>
<mat-cell *matCellDef="let element">
{{ element.registrationExpirationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="registrationExpirationTime">
<mat-header-cell *matHeaderCellDef>Expiration Time</mat-header-cell>
<mat-cell *matCellDef="let element">
{{ element.registrationExpirationTime }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="statuses">
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.statuses }}</mat-cell>
</ng-container>
<ng-container matColumnDef="statuses">
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.statuses }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
<!-- Row shown when there is no matching data. -->
<mat-row *matNoDataRow>
<mat-cell colspan="4">No domains found</mat-cell>
</mat-row>
</mat-table>
<mat-paginator
[length]="totalResults"
[pageIndex]="pageNumber"
[pageSize]="resultsPerPage"
[pageSizeOptions]="[10, 25, 50, 100, 500]"
(page)="onPageChange($event)"
aria-label="Select page of domain results"
showFirstLastButtons
></mat-paginator>
<!-- Row shown when there is no matching data. -->
<mat-row *matNoDataRow>
<mat-cell colspan="4">No domains found</mat-cell>
</mat-row>
</mat-table>
<mat-paginator
[length]="totalResults"
[pageIndex]="pageNumber"
[pageSize]="resultsPerPage"
[pageSizeOptions]="[10, 25, 50, 100, 500]"
(page)="onPageChange($event)"
aria-label="Select page of domain results"
showFirstLastButtons
></mat-paginator>
</div>
}
</div>
</app-selected-registrar-wrapper>
@@ -12,13 +12,10 @@
}
}
&-domains {
position: relative;
&__download {
position: absolute;
top: 0;
right: 0;
}
&-domains__download {
position: absolute;
top: -55px;
right: 0;
}
&__domains {
@@ -31,10 +28,26 @@
width: 100%;
}
&__domains-table-parent {
position: relative;
width: fit-content;
min-width: 100%;
}
&__domains-table {
min-width: $min-width !important;
}
&__domains-spinner {
align-items: center;
display: flex;
justify-content: center;
position: absolute;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.6);
}
.mat-mdc-paginator {
min-width: $min-width !important;
}
@@ -14,11 +14,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DomainListComponent } from './domainList.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DomainListComponent } from './domainList.component';
describe('DomainListComponent', () => {
let component: DomainListComponent;
@@ -27,12 +28,12 @@ describe('DomainListComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DomainListComponent],
imports: [
HttpClientTestingModule,
MaterialModule,
BrowserAnimationsModule,
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
providers: [BackendService],
}).compileComponents();
fixture = TestBed.createComponent(DomainListComponent);
@@ -58,11 +58,10 @@ export class DomainListComponent {
private _snackBar: MatSnackBar
) {
effect(() => {
if (this.registrarService.registrarId()) {
this.pageNumber = 0;
this.totalResults = 0;
this.reloadData();
}
this.pageNumber = 0;
this.totalResults = 0;
this.reloadData();
this.registrarService.registrarId();
});
}
@@ -0,0 +1,182 @@
<div class="console-new-registrar">
<button
mat-icon-button
aria-label="Back to registrars list"
(click)="goBack()"
>
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
<h1>Create a registrar</h1>
<form (ngSubmit)="save($event)" #form>
<h2>General</h2>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar Name: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.registrarName"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.registrarId"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Registrar email address: </mat-label>
<input
matInput
type="email"
[required]="true"
[(ngModel)]="newRegistrar.emailAddress"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Billing Accounts: </mat-label>
<textarea
matInput
required="true"
placeholder="USD=billing-id-for-usd
JPY=billing-id-for-yen"
[ngModel]="billingAccountMap"
(ngModelChange)="onBillingAccountMapChange($event)"
[ngModelOptions]="{ standalone: true }"
></textarea>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>IANA ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.ianaIdentifier"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>ICANN referral email: </mat-label>
<input
matInput
[required]="true"
type="email"
[(ngModel)]="newRegistrar.icannReferralEmail"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Drive ID: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.driveFolderId"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<h2>Contact Info</h2>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 1): </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="localizedAddressStreet.line1"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 2)</mat-label>
<input
matInput
[required]="false"
[(ngModel)]="localizedAddressStreet.line2"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Street address (Line 3)</mat-label>
<input
matInput
[required]="false"
[(ngModel)]="localizedAddressStreet.line3"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>City: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.city"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>State/Region: </mat-label>
<input
matInput
[required]="false"
[(ngModel)]="newRegistrar.localizedAddress.state"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>ZIP/Postal Code: </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.zip"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<section>
<mat-form-field appearance="outline">
<mat-label>Country Code (e.g. US): </mat-label>
<input
matInput
[required]="true"
[(ngModel)]="newRegistrar.localizedAddress.countryCode"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
</section>
<button
class="console-new-registrar__submit"
mat-flat-button
color="primary"
type="submit"
>
Save
</button>
</form>
</div>
@@ -0,0 +1,20 @@
.console-new-registrar {
max-width: 616px;
h2 {
margin: 40px 0 25px 0 !important;
}
section {
margin-bottom: 20px;
}
mat-form-field {
display: block;
width: 100%;
}
&__submit {
margin: 30px 0;
}
}
@@ -0,0 +1,99 @@
// 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.
import { HttpErrorResponse } from '@angular/common/http';
import {
Component,
ElementRef,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Registrar, RegistrarService } from './registrar.service';
interface LocalizedAddressStreet {
line1: string;
line2: string;
line3: string;
}
@Component({
selector: 'app-new-registrar',
templateUrl: './newRegistrar.component.html',
styleUrls: ['./newRegistrar.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export default class NewRegistrarComponent {
protected newRegistrar: Registrar;
protected localizedAddressStreet: LocalizedAddressStreet;
protected billingAccountMap: String = '';
@ViewChild('form') form!: ElementRef;
constructor(
private registrarService: RegistrarService,
private _snackBar: MatSnackBar
) {
this.newRegistrar = {
registrarId: '',
url: '',
whoisServer: '',
registrarName: '',
icannReferralEmail: '',
localizedAddress: {
city: '',
state: '',
zip: '',
countryCode: '',
},
};
this.localizedAddressStreet = {
line1: '',
line2: '',
line3: '',
};
}
onBillingAccountMapChange(val: String) {
const billingAccountMap: { [key: string]: string } = {};
this.newRegistrar.billingAccountMap = val.split('\n').reduce((acc, val) => {
const [currency, billingCode] = val.split('=');
acc[currency] = billingCode;
return acc;
}, billingAccountMap);
}
save(e: SubmitEvent) {
e.preventDefault();
if (this.form.nativeElement.checkValidity()) {
const { line1, line2, line3 } = this.localizedAddressStreet;
this.newRegistrar.localizedAddress.street = [line1, line2, line3].filter(
(v) => !!v
);
this.registrarService.createRegistrar(this.newRegistrar).subscribe({
complete: () => {
this.goBack();
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
});
} else {
this.form.nativeElement.reportValidity();
}
}
goBack() {
this.registrarService.inNewRegistrarMode.set(false);
}
}
@@ -14,18 +14,24 @@
import { TestBed } from '@angular/core/testing';
import { RegistrarService } from './registrar.service';
import { BackendService } from '../shared/services/backend.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { MatSnackBar } from '@angular/material/snack-bar';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarService } from './registrar.service';
describe('RegistrarService', () => {
let service: RegistrarService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [BackendService, MatSnackBar],
imports: [],
providers: [
BackendService,
MatSnackBar,
provideHttpClient(),
provideHttpClientTesting(),
],
});
service = TestBed.inject(RegistrarService);
});
@@ -13,7 +13,7 @@
// limitations under the License.
import { Injectable, computed, signal } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { Observable, switchMap, tap } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
@@ -85,14 +85,21 @@ export class RegistrarService implements GlobalLoader {
this.registrars().find((r) => r.registrarId === this.registrarId())
);
inNewRegistrarMode = signal(false);
registrarsLoaded: Promise<void>;
constructor(
private backend: BackendService,
private globalLoader: GlobalLoaderService,
private _snackBar: MatSnackBar,
private router: Router
) {
this.loadRegistrars().subscribe((r) => {
this.globalLoader.stopGlobalLoader(this);
this.registrarsLoaded = new Promise((resolve) => {
this.loadRegistrars().subscribe((r) => {
this.globalLoader.stopGlobalLoader(this);
resolve();
});
});
this.globalLoader.startGlobalLoader(this);
}
@@ -118,19 +125,23 @@ export class RegistrarService implements GlobalLoader {
);
}
saveRegistrar(registrar: Registrar) {
return this.backend.postRegistrar(registrar).pipe(
tap((registrar) => {
if (registrar) {
this.registrars.set(
this.registrars().map((r) => {
if (r.registrarId === registrar.registrarId) {
return registrar;
}
return r;
})
);
}
createRegistrar(registrar: Registrar) {
return this.backend
.createRegistrar(registrar)
.pipe(switchMap((_) => this.loadRegistrars()));
}
updateRegistrar(updatedRegistrar: Registrar) {
return this.backend.updateRegistrar(updatedRegistrar).pipe(
tap(() => {
this.registrars.set(
this.registrars().map((r) => {
if (r.registrarId === updatedRegistrar.registrarId) {
return updatedRegistrar;
}
return r;
})
);
})
);
}
@@ -42,28 +42,32 @@ export class RegistrarDetailsComponent implements OnInit {
) {}
ngOnInit(): void {
this.subscription = this.route.paramMap.subscribe((params: ParamMap) => {
this.registrarInEdit = structuredClone(
this.registrarService
.registrars()
.filter((r) => r.registrarId === params.get('id'))[0]
);
if (!this.registrarInEdit) {
this._snackBar.open(
`Registrar with id ${params.get('id')} is not available`
this.registrarService.registrarsLoaded.then(() => {
this.subscription = this.route.paramMap.subscribe((params: ParamMap) => {
this.registrarInEdit = structuredClone(
this.registrarService
.registrars()
.filter((r) => r.registrarId === params.get('id'))[0]
);
this.registrarNotFound = true;
} else {
this.registrarNotFound = false;
}
if (!this.registrarInEdit) {
this._snackBar.open(
`Registrar with id ${params.get('id')} is not available`
);
this.registrarNotFound = true;
} else {
this.registrarNotFound = false;
}
});
});
}
addTLD(e: MatChipInputEvent) {
this.registrarInEdit.allowedTlds = this.registrarInEdit.allowedTlds || [];
this.removeTLD(e.value); // Prevent dups
this.registrarInEdit.allowedTlds = this.registrarInEdit.allowedTlds?.concat(
[e.value.toLowerCase()]
);
this.registrarInEdit.allowedTlds = [
...this.registrarInEdit.allowedTlds,
e.value.toLowerCase(),
];
}
removeTLD(tld: string) {
@@ -73,7 +77,7 @@ export class RegistrarDetailsComponent implements OnInit {
}
saveAndClose() {
this.registrarService.saveRegistrar(this.registrarInEdit).subscribe({
this.registrarService.updateRegistrar(this.registrarInEdit).subscribe({
complete: () => {
this.router.navigate([RegistrarComponent.PATH], {
queryParamsHandling: 'merge',
@@ -1,9 +1,5 @@
<div class="console-app__registrar">
<mat-form-field
class="example-full-width"
class="mat-form-field-density-5"
appearance="outline"
>
<mat-form-field class="example-full-width" appearance="outline">
<mat-label>Registrar</mat-label>
<input
type="text"
@@ -14,11 +14,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistrarSelectorComponent } from './registrarSelector.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RegistrarSelectorComponent } from './registrarSelector.component';
describe('RegistrarSelectorComponent', () => {
let component: RegistrarSelectorComponent;
@@ -26,13 +27,13 @@ describe('RegistrarSelectorComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
MaterialModule,
BrowserAnimationsModule,
],
providers: [BackendService],
declarations: [RegistrarSelectorComponent],
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
fixture = TestBed.createComponent(RegistrarSelectorComponent);
@@ -1,5 +1,19 @@
@if(registrarService.inNewRegistrarMode()) {
<app-new-registrar />
} @else {
<div class="console-app__registrars">
<h1 class="mat-headline-4">Registrars</h1>
<div class="console-app__registrars-header">
<h1 class="mat-headline-4">Registrars</h1>
<button
mat-flat-button
color="primary"
(click)="openNewRegistrar()"
aria-label="Add new registrar"
>
<mat-icon>add</mat-icon>
Add new registrar
</button>
</div>
<mat-form-field class="console-app__registrars-filter">
<mat-label>Search</mat-label>
<input
@@ -36,3 +50,5 @@
showFirstLastButtons
></mat-paginator>
</div>
}
@@ -15,6 +15,11 @@
min-width: $min-width !important;
}
&__registrars-header {
display: flex;
justify-content: space-between;
}
.mat-mdc-paginator {
min-width: $min-width !important;
}
@@ -14,12 +14,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistrarComponent } from './registrarsTable.component';
import { BackendService } from '../shared/services/backend.service';
import { ActivatedRoute } from '@angular/router';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ActivatedRoute } from '@angular/router';
import { MaterialModule } from '../material.module';
import { BackendService } from '../shared/services/backend.service';
import { RegistrarComponent } from './registrarsTable.component';
describe('RegistrarComponent', () => {
let component: RegistrarComponent;
@@ -28,14 +29,12 @@ describe('RegistrarComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RegistrarComponent],
imports: [
HttpClientTestingModule,
MaterialModule,
BrowserAnimationsModule,
],
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
{ provide: ActivatedRoute, useValue: {} as ActivatedRoute },
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, effect, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
@@ -93,6 +93,9 @@ export class RegistrarComponent {
this.dataSource = new MatTableDataSource<Registrar>(
registrarService.registrars()
);
effect(() => {
this.dataSource.data = registrarService.registrars();
});
}
ngAfterViewInit() {
@@ -111,4 +114,8 @@ export class RegistrarComponent {
// TODO: consider filteing out only by registrar name
this.dataSource.filter = filterValue.trim().toLowerCase();
}
openNewRegistrar() {
this.registrarService.inNewRegistrarMode.set(true);
}
}
@@ -14,10 +14,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import ContactComponent from './contact.component';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { MaterialModule } from 'src/app/material.module';
import { BackendService } from 'src/app/shared/services/backend.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import ContactComponent from './contact.component';
describe('ContactComponent', () => {
let component: ContactComponent;
@@ -26,8 +27,12 @@ describe('ContactComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ContactComponent],
imports: [HttpClientTestingModule, MaterialModule],
providers: [BackendService],
imports: [MaterialModule],
providers: [
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
fixture = TestBed.createComponent(ContactComponent);
component = fixture.componentInstance;
@@ -26,6 +26,6 @@
mat-form-field {
display: block;
width: 100%;
margin-bottom: 30px;
margin-bottom: 20px;
}
}
@@ -14,18 +14,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import SecurityComponent from './security.component';
import { SecurityService, apiToUiConverter } from './security.service';
import { BackendService } from 'src/app/shared/services/backend.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { MaterialModule } from 'src/app/material.module';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { of } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { MaterialModule } from 'src/app/material.module';
import {
Registrar,
RegistrarService,
} from 'src/app/registrar/registrar.service';
import { BackendService } from 'src/app/shared/services/backend.service';
import SecurityComponent from './security.component';
import { SecurityService, apiToUiConverter } from './security.service';
describe('SecurityComponent', () => {
let component: SecurityComponent;
@@ -50,16 +51,13 @@ describe('SecurityComponent', () => {
} as RegistrarService;
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
MaterialModule,
BrowserAnimationsModule,
FormsModule,
],
declarations: [SecurityComponent],
imports: [MaterialModule, BrowserAnimationsModule, FormsModule],
providers: [
BackendService,
{ provide: RegistrarService, useValue: dummyRegistrarService },
provideHttpClient(),
provideHttpClientTesting(),
],
})
.overrideComponent(SecurityComponent, {
@@ -14,6 +14,11 @@
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { MatSnackBar } from '@angular/material/snack-bar';
import { BackendService } from 'src/app/shared/services/backend.service';
import SecurityComponent from './security.component';
import {
SecurityService,
SecuritySettings,
@@ -21,10 +26,6 @@ import {
apiToUiConverter,
uiToApiConverter,
} from './security.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import SecurityComponent from './security.component';
import { BackendService } from 'src/app/shared/services/backend.service';
import { MatSnackBar } from '@angular/material/snack-bar';
describe('SecurityService', () => {
const uiMockData: SecuritySettings = {
@@ -42,9 +43,15 @@ describe('SecurityService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [SecurityComponent],
providers: [MatSnackBar, SecurityService, BackendService],
imports: [],
providers: [
MatSnackBar,
SecurityService,
BackendService,
provideHttpClient(),
provideHttpClientTesting(),
],
});
service = TestBed.inject(SecurityService);
});
@@ -29,7 +29,6 @@
>Security</a
>
</nav>
<mat-divider></mat-divider>
<mat-tab-nav-panel #tabPanel>
<router-outlet></router-outlet>
</mat-tab-nav-panel>
@@ -13,9 +13,6 @@
// limitations under the License.
.console-settings {
> mat-divider {
margin-bottom: 40px;
}
.mdc-tab {
&.active-link {
border-bottom: 2px solid var(--primary);
@@ -24,4 +21,7 @@
}
}
}
nav {
margin-bottom: 40px;
}
}
@@ -5,7 +5,7 @@
display: flex;
align-items: center;
gap: 1rem;
margin: 20px 0;
margin-bottom: 20px;
button {
flex-shrink: 0;
}
@@ -14,12 +14,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import WhoisComponent from './whois.component';
import { MaterialModule } from 'src/app/material.module';
import { BackendService } from 'src/app/shared/services/backend.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from 'src/app/material.module';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { BackendService } from 'src/app/shared/services/backend.service';
import WhoisComponent from './whois.component';
describe('WhoisComponent', () => {
let component: WhoisComponent;
@@ -28,14 +29,12 @@ describe('WhoisComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [WhoisComponent],
imports: [
HttpClientTestingModule,
MaterialModule,
BrowserAnimationsModule,
],
imports: [MaterialModule, BrowserAnimationsModule],
providers: [
BackendService,
{ provide: RegistrarService, useValue: { registrar: {} } },
provideHttpClient(),
provideHttpClientTesting(),
],
}).compileComponents();
@@ -110,7 +110,13 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<Registrar[]>(err)));
}
postRegistrar(registrar: Registrar): Observable<Registrar> {
createRegistrar(registrar: Registrar): Observable<Registrar> {
return this.http
.post<Registrar>('/console-api/registrars', registrar)
.pipe(catchError((err) => this.errorCatcher<Registrar>(err)));
}
updateRegistrar(registrar: Registrar): Observable<Registrar> {
return this.http
.post<Registrar>('/console-api/registrar', registrar)
.pipe(catchError((err) => this.errorCatcher<Registrar>(err)));
+3 -3
View File
@@ -30,14 +30,14 @@ body {
.console-app {
.mat-headline-4 {
margin-top: 6px;
margin-bottom: 24px;
margin-top: 6px !important;
margin-bottom: 24px !important;
}
.spacer {
flex: 1;
}
h1 {
margin-bottom: 24px;
margin-bottom: 24px !important;
}
a {
text-decoration: none;
+52 -85
View File
@@ -4,63 +4,19 @@
$secondary-color: #80868b;
$border-color: #dadce0;
$hue-undefined: "undefined";
$blue-palette: (
50: #e8f0fe,
100: #d2e3fc,
200: #aecbfa,
300: #8ab4f8,
400: #669df6,
500: #4285f4,
600: #1a73e8,
700: #1967d2,
800: #185abc,
900: #174ea6,
A100: $hue-undefined,
A200: $hue-undefined,
A400: $hue-undefined,
A700: $hue-undefined,
contrast: (
50: #174ea6,
100: #174ea6,
200: #174ea6,
300: #174ea6,
400: #174ea6,
500: white,
600: white,
700: white,
800: white,
900: white,
A100: $hue-undefined,
A200: $hue-undefined,
A400: $hue-undefined,
A700: $hue-undefined,
),
);
@function rem($valueInPixels, $rootbase: 16px) {
@return math.div($valueInPixels, $rootbase) * 1rem;
}
/** Copied from docs section **/
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat.core();
// The warn palette is optional (defaults to red).
$theme-warn: mat.define-palette(mat.$red-palette);
/**
** Application specific section - Global styles and mixins
**/
$theme-primary: mat.define-palette($blue-palette);
$typographyConfig: mat.define-typography-config(
$typographyConfig: mat.m2-define-typography-config(
$headline-1:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(29px),
rem(36px),
500,
@@ -68,7 +24,7 @@ $typographyConfig: mat.define-typography-config(
normal
),
$headline-4:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(28px),
rem(36px),
500,
@@ -76,7 +32,7 @@ $typographyConfig: mat.define-typography-config(
normal
),
$headline-5:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(20px),
rem(28px),
400,
@@ -84,9 +40,15 @@ $typographyConfig: mat.define-typography-config(
normal
),
$headline-6:
mat.define-typography-level(rem(16px), rem(2px), 500, "Google Sans", normal),
mat.m2-define-typography-level(
rem(16px),
rem(2px),
500,
"Google Sans",
normal
),
$body-1:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(16px),
rem(24px),
400,
@@ -94,7 +56,7 @@ $typographyConfig: mat.define-typography-config(
normal
),
$body-2:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(14px),
rem(20px),
400,
@@ -102,7 +64,7 @@ $typographyConfig: mat.define-typography-config(
normal
),
$caption:
mat.define-typography-level(
mat.m2-define-typography-level(
rem(14px),
rem(24px),
400,
@@ -110,37 +72,13 @@ $typographyConfig: mat.define-typography-config(
0.15px
),
$overline:
mat.define-typography-level(rem(14px), rem(20px), 500, "Google Sans", 0.5px),
);
@include mat.typography-hierarchy($typographyConfig);
@mixin form-field-density($density) {
$field-typography: mat.define-typography-config(
$body-1: mat.define-typography-level(12px, 24px, 400),
);
@include mat.typography-level($field-typography, "body-1");
@include mat.form-field-density($density);
}
// Define lowest possible density class to be used in application
// In the same manner -1...-5 classes can be defined
.mat-form-field-density-5 {
@include form-field-density(-5);
}
/**
** Light theme
**/
$light-theme: mat.define-light-theme(
(
color: (
primary: $theme-primary,
accent: $theme-primary,
warn: $theme-warn,
mat.m2-define-typography-level(
rem(14px),
rem(20px),
500,
"Google Sans",
0.5px
),
density: 0,
typography: $typographyConfig,
)
);
// Access and define a class with secondary color exposed
@@ -165,12 +103,41 @@ mat-row:hover {
:root {
--text: #5f6368;
--primary: #{mat.get-color-from-palette($blue-palette, 500)};
--lightest: #{mat.get-color-from-palette($blue-palette, 100)};
--primary: #4285f4;
--lightest: #d2e3fc;
--light-highlight: #e8eaed;
--lightest-highlight: #f8f9fa;
--secondary: #{$secondary-color};
--border: #{$border-color};
--mat-tree-node-text-font: "Google Sans Text";
--mat-tree-node-text-size: 0.95rem;
--mat-sidenav-container-width: 280px;
}
@include mat.all-component-themes($light-theme);
$theme: mat.define-theme(
(
color: (
theme-type: light,
primary: mat.$blue-palette,
),
typography: (
plain-family: "Google Sans",
brand-family: "Google Sans Text",
bold-weight: 600,
),
)
);
html {
@include mat.all-component-themes($theme);
@include mat.typography-hierarchy($typographyConfig);
@include mat.form-field-theme(
mat.define-theme(
(
density: (
scale: -3,
),
)
)
);
}
+1 -1
View File
@@ -5,6 +5,7 @@
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
@@ -12,7 +13,6 @@
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
+2 -5
View File
@@ -135,14 +135,12 @@ dependencies {
implementation deps['com.google.api.grpc:proto-google-common-protos']
implementation deps['com.google.api.grpc:proto-google-cloud-secretmanager-v1']
implementation deps['com.google.api-client:google-api-client']
implementation deps['com.google.api-client:google-api-client-appengine']
implementation deps['com.google.api-client:google-api-client-servlet']
implementation deps['com.google.monitoring-client:metrics']
implementation deps['com.google.monitoring-client:stackdriver']
implementation deps['com.google.api-client:google-api-client-java6']
implementation deps['com.google.api.grpc:proto-google-cloud-tasks-v2']
implementation deps['com.google.apis:google-api-services-admin-directory']
implementation deps['com.google.apis:google-api-services-appengine']
implementation deps['com.google.apis:google-api-services-bigquery']
implementation deps['com.google.apis:google-api-services-dataflow']
implementation deps['com.google.apis:google-api-services-dns']
@@ -176,12 +174,10 @@ dependencies {
implementation deps['com.google.cloud:google-cloud-storage']
implementation deps['com.google.cloud:google-cloud-tasks']
implementation deps['com.google.http-client:google-http-client']
implementation deps['com.google.http-client:google-http-client-appengine']
implementation deps['com.google.http-client:google-http-client-jackson2']
implementation deps['com.google.oauth-client:google-oauth-client']
implementation deps['com.google.oauth-client:google-oauth-client-java6']
implementation deps['com.google.oauth-client:google-oauth-client-jetty']
implementation deps['com.google.oauth-client:google-oauth-client-appengine']
implementation deps['com.google.oauth-client:google-oauth-client-servlet']
implementation deps['com.google.protobuf:protobuf-java']
implementation deps['com.google.re2j:re2j']
@@ -194,8 +190,8 @@ dependencies {
testRuntimeOnly deps['guru.nidi:graphviz-java-all-j2v8']
testImplementation deps['io.github.classgraph:classgraph']
testRuntimeOnly deps['io.github.java-diff-utils:java-diff-utils']
implementation deps['javax.mail:mail']
implementation deps['jakarta.inject:jakarta.inject-api']
implementation deps['jakarta.mail:jakarta.mail-api']
implementation deps['javax.persistence:javax.persistence-api']
implementation deps['jakarta.servlet:jakarta.servlet-api']
implementation deps['javax.xml.bind:jaxb-api']
@@ -247,6 +243,7 @@ dependencies {
implementation deps['us.fatehi:schemacrawler-api']
implementation deps['us.fatehi:schemacrawler-diagram']
implementation deps['us.fatehi:schemacrawler-tools']
implementation deps['us.fatehi:schemacrawler-postgresql']
implementation deps['xerces:xmlParserAPIs']
implementation deps['org.ogce:xpp3']
// Known issue: nebula-lint misses inherited dependency.
+40 -39
View File
@@ -40,11 +40,10 @@ com.github.jnr:jnr-unixsocket:0.38.22=compileClasspath,deploy_jar,nonprodCompile
com.github.jnr:jnr-x86asm:1.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.android:annotations:4.1.1.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-appengine:2.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-jackson2:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-java6:2.1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client:1.35.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client:2.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.32.1-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.39.0-alpha=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -88,12 +87,11 @@ com.google.api:gax-grpc:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspa
com.google.api:gax-httpjson:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.48.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20240509-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-appengine:v1-rev20240415-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20240229-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20240423-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20240310-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20240218-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v2beta1-rev99-1.25.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v2-rev393-1.25.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dataflow:v1b3-rev20240430-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20240419-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20240521-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-gmail:v1-rev20240520-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20210624-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -112,7 +110,8 @@ com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compi
com.google.auto.service:auto-service:1.1.1=annotationProcessor
com.google.auto.value:auto-value-annotations:1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.auto.value:auto-value:1.10.4=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value:1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.value:auto-value:1.11.0=annotationProcessor,testAnnotationProcessor
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.closure-stylesheets:closure-stylesheets:1.5.0=css
com.google.cloud.bigdataoss:gcsio:2.2.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -170,19 +169,19 @@ com.google.googlejavaformat:google-java-format:1.5=annotationProcessor,testAnnot
com.google.guava:failureaccess:1.0.1=checkstyle,errorprone,nonprodAnnotationProcessor,soy
com.google.guava:failureaccess:1.0.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava-parent:32.1.1-jre=errorprone,nonprodAnnotationProcessor
com.google.guava:guava-testlib:33.2.0-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:guava-testlib:33.2.1-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:20.0=css
com.google.guava:guava:31.0.1-jre=checkstyle,soy
com.google.guava:guava:32.1.1-jre=errorprone,nonprodAnnotationProcessor
com.google.guava:guava:33.0.0-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.2.0-android=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.guava:guava:33.2.0-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:33.2.1-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.43.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-apache-v2:1.44.1=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.44.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-gson:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:1.44.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.43.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-appengine:1.44.1=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-gson:1.44.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-protobuf:1.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:1.44.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -200,7 +199,6 @@ com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,non
com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:metrics:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:stackdriver:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client-appengine:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client-java6:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client-jetty:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.oauth-client:google-oauth-client-servlet:1.36.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -313,20 +311,19 @@ io.opentelemetry:opentelemetry-context:1.36.0=compileClasspath,deploy_jar,nonpro
io.outfoxx:swiftpoet:1.3.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.perfmark:perfmark-api:0.27.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.3=jaxb
jakarta.activation:jakarta.activation-api:2.1.3=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:1.0.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=soy
jakarta.mail:jakarta.mail-api:2.1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.0.0=testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.1.0-M2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.2=jaxb
javacc:javacc:4.1=css
javax.activation:activation:1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.activation:javax.activation-api:1.2.0=compileClasspath,deploy_jar,jaxb,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,errorprone,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.mail:mail:1.5.0-b01=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.persistence:javax.persistence-api:2.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.xml.bind:jaxb-api:2.3.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -398,20 +395,22 @@ org.checkerframework:checker-qual:3.42.0=compileClasspath,deploy_jar,nonprodComp
org.codehaus.mojo:animal-sniffer-annotations:1.23=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.easymock:easymock:3.0=css
org.eclipse.angus:angus-activation:2.0.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.angus:jakarta.mail:2.0.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.eclipse.collections:eclipse-collections-api:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.collections:eclipse-collections:11.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-ee:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-http:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-io:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-security:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-server:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.0.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.0.9=testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:10.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:10.13.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty.ee10:jetty-ee10-webapp:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-ee:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-http:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-io:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-security:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-server:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.0.10=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.0.10=testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:10.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:10.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-runtime:2.3.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:txw2:2.3.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.gwtproject:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -489,7 +488,7 @@ org.ow2.asm:asm:7.0=soy
org.ow2.asm:asm:9.2=compileClasspath,nonprodCompileClasspath
org.ow2.asm:asm:9.6=deploy_jar,jacocoAnt,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
org.postgresql:postgresql:42.7.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.postgresql:postgresql:42.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-api:3.141.59=testCompileClasspath,testRuntimeClasspath
@@ -504,9 +503,8 @@ org.seleniumhq.selenium:selenium-safari-driver:3.141.59=testCompileClasspath,tes
org.seleniumhq.selenium:selenium-support:3.141.59=testCompileClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:1.7.32=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=compileClasspath,nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath
org.slf4j:slf4j-api:2.1.0-alpha1=deploy_jar,runtimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-jdk14:2.1.0-alpha1=deploy_jar,runtimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-jdk14:2.0.12=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.19.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.testcontainers:jdbc:1.19.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -519,10 +517,13 @@ org.w3c.css:sac:1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodR
org.webjars.npm:viz.js-graphviz-java:2.1.3=testRuntimeClasspath
org.xerial.snappy:snappy-java:1.1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.yaml:snakeyaml:2.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-api:16.10.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:16.10.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-tools:16.10.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-utility:16.10.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:16.10.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-api:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-diagram:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-loader:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-postgresql:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-text:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-tools:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler-utility:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
us.fatehi:schemacrawler:16.21.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
xerces:xmlParserAPIs:2.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
empty=devtool,nomulus_test
@@ -41,10 +41,10 @@ import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.util.DateTimeUtils;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.joda.time.Duration;
/** Task that re-locks a previously-Registry-Locked domain after a predetermined period of time. */
@@ -38,10 +38,10 @@ import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
@@ -17,8 +17,8 @@ package google.registry.bsa;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
/** Sends BSA-related email notifications. */
class BsaEmailSender {
@@ -39,6 +39,8 @@ import google.registry.model.common.DnsRefreshRequest;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.RegistryEnvironment;
import google.registry.util.YamlUtils;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.net.URI;
@@ -54,8 +56,6 @@ import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.Duration;
@@ -63,11 +63,11 @@ import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.DomainNameUtils;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -401,7 +401,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
if (err == null || err.getErrors().size() > 1) {
throw e;
}
String errorReason = err.getErrors().get(0).getReason();
String errorReason = err.getErrors().getFirst().getReason();
if (RETRYABLE_EXCEPTION_REASONS.contains(errorReason)) {
throw new ZoneStateException(errorReason);
@@ -15,6 +15,7 @@
package google.registry.flows;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.Strings;
@@ -37,6 +38,7 @@ import google.registry.model.host.HostHistory;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.IsolationLevel;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.JpaTransactionManager;
import java.lang.annotation.Documented;
import java.util.Optional;
import javax.inject.Qualifier;
@@ -191,6 +193,16 @@ public class FlowModule {
}
}
@Provides
@FlowScope
static JpaTransactionManager provideJpaTm(Class<? extends Flow> flowClass) {
if (MutatingFlow.class.isAssignableFrom(flowClass)) {
return tm();
} else {
return replicaTm();
}
}
@Provides
@FlowScope
static ResourceCommand provideResourceCommand(EppInput eppInput) {
@@ -14,7 +14,6 @@
package google.registry.flows;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.xml.XmlTransformer.prettyPrint;
import com.google.common.flogger.FluentLogger;
@@ -28,6 +27,7 @@ import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppOutput;
import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.JpaTransactionManager;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -52,6 +52,8 @@ public class FlowRunner {
@Inject SessionMetadata sessionMetadata;
@Inject Trid trid;
@Inject FlowReporter flowReporter;
@Inject JpaTransactionManager jpaTransactionManager;
@Inject FlowRunner() {}
/** Runs the EPP flow, and records metrics on the given builder. */
@@ -77,26 +79,24 @@ public class FlowRunner {
return EppOutput.create(flowProvider.get().run());
}
try {
// TODO(mcilwain/weiminyu): Use transactReadOnly() here for TransactionalFlow and transact()
// for MutatingFlow.
return tm().transact(
isolationLevelOverride.orElse(null),
() -> {
try {
EppOutput output = EppOutput.create(flowProvider.get().run());
if (isDryRun) {
throw new DryRunException(output);
}
if (flowClass.equals(LoginFlow.class)) {
// In LoginFlow, registrarId isn't known until after the flow executes, so save
// it then.
eppMetricBuilder.setRegistrarId(sessionMetadata.getRegistrarId());
}
return output;
} catch (EppException e) {
throw new EppRuntimeException(e);
}
});
return jpaTransactionManager.transact(
isolationLevelOverride.orElse(null),
() -> {
try {
EppOutput output = EppOutput.create(flowProvider.get().run());
if (isDryRun) {
throw new DryRunException(output);
}
if (flowClass.equals(LoginFlow.class)) {
// In LoginFlow, registrarId isn't known until after the flow executes, so save
// it then.
eppMetricBuilder.setRegistrarId(sessionMetadata.getRegistrarId());
}
return output;
} catch (EppException e) {
throw new EppRuntimeException(e);
}
});
} catch (DryRunException e) {
return e.output;
} catch (EppRuntimeException e) {
@@ -15,7 +15,6 @@
package google.registry.flows;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.xml.ValidationMode.LENIENT;
import static google.registry.xml.ValidationMode.STRICT;
@@ -103,7 +102,7 @@ public final class FlowUtils {
}
public static HistoryEntryId createHistoryEntryId(EppResource parent) {
return new HistoryEntryId(parent.getRepoId(), allocateId());
return new HistoryEntryId(parent.getRepoId(), tm().allocateId());
}
/** Registrar is not logged in. */
@@ -19,7 +19,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
@@ -79,7 +78,7 @@ public final class ContactCreateFlow implements MutatingFlow {
.setAuthInfo(command.getAuthInfo())
.setCreationRegistrarId(registrarId)
.setPersistedCurrentSponsorRegistrarId(registrarId)
.setRepoId(createRepoId(allocateId(), roidSuffix))
.setRepoId(createRepoId(tm().allocateId(), roidSuffix))
.setFaxNumber(command.getFax())
.setVoiceNumber(command.getVoice())
.setDisclose(command.getDisclose())
@@ -33,6 +33,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE;
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
import static google.registry.model.tld.label.ReservationType.getTypeOfHighestSeverity;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -188,7 +189,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
ImmutableSet<InternetDomainName> bsaBlockedDomainNames =
existingDomains.size() == parsedDomains.size()
? ImmutableSet.of()
: getBsaBlockedDomains(parsedDomains.values());
: getBsaBlockedDomains(parsedDomains.values(), now);
Optional<AllocationTokenExtension> allocationTokenExtension =
eppInput.getSingleExtension(AllocationTokenExtension.class);
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
@@ -441,9 +442,11 @@ public final class DomainCheckFlow implements TransactionalFlow {
}
static ImmutableSet<InternetDomainName> getBsaBlockedDomains(
ImmutableCollection<InternetDomainName> parsedDomains) {
ImmutableCollection<InternetDomainName> parsedDomains, DateTime now) {
Map<String, ImmutableList<InternetDomainName>> labelToDomainNames =
parsedDomains.stream()
.filter(
parsedDomain -> isEnrolledWithBsa(Tld.get(parsedDomain.parent().toString()), now))
.collect(
Collectors.groupingBy(
parsedDomain -> parsedDomain.parts().get(0), toImmutableList()));
@@ -46,7 +46,6 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNo
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
import static google.registry.model.IdService.allocateId;
import static google.registry.model.eppcommon.StatusValue.SERVER_HOLD;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
@@ -345,8 +344,8 @@ public final class DomainCreateFlow implements MutatingFlow {
Optional<SecDnsCreateExtension> secDnsCreate =
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
String repoId = createDomainRepoId(allocateId(), tld.getTldStr());
long historyRevisionId = allocateId();
String repoId = createDomainRepoId(tm().allocateId(), tld.getTldStr());
long historyRevisionId = tm().allocateId();
HistoryEntryId domainHistoryId = new HistoryEntryId(repoId, historyRevisionId);
historyBuilder.setRevisionId(historyRevisionId);
// Bill for the create.
@@ -22,7 +22,6 @@ import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.IdService.allocateId;
import static google.registry.model.reporting.HistoryEntry.Type.HOST_CREATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
@@ -123,7 +122,7 @@ public final class HostCreateFlow implements MutatingFlow {
.setPersistedCurrentSponsorRegistrarId(registrarId)
.setHostName(targetId)
.setInetAddresses(command.getInetAddresses())
.setRepoId(createRepoId(allocateId(), roidSuffix))
.setRepoId(createRepoId(tm().allocateId(), roidSuffix))
.setSuperordinateDomain(superordinateDomain.map(Domain::createVKey).orElse(null))
.build();
historyBuilder.setType(HOST_CREATE).setHost(newHost);
@@ -28,22 +28,22 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.util.EmailMessage;
import google.registry.util.EmailMessage.Attachment;
import google.registry.util.Retrier;
import jakarta.mail.Address;
import jakarta.mail.BodyPart;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Session;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
/** Sends {@link EmailMessage EmailMessages} through Google Workspace using {@link Gmail}. */
public final class GmailClient {
@@ -16,8 +16,8 @@ package google.registry.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.IdService.allocateId;
import static google.registry.model.ModelUtils.getAllFields;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import google.registry.model.annotations.IdAllocation;
import google.registry.util.TypeUtils.TypeInstantiator;
@@ -65,7 +65,7 @@ public interface Buildable {
&& !idField.getType().equals(String.class)
&& Optional.ofNullable((Long) ModelUtils.getFieldValue(instance, idField))
.orElse(0L) == 0) {
ModelUtils.setFieldValue(instance, idField, allocateId());
ModelUtils.setFieldValue(instance, idField, tm().reTransact(tm()::allocateId));
}
return instance;
} finally {
@@ -14,15 +14,14 @@
package google.registry.model.annotations;
import google.registry.model.IdService;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is needed for any ID field that needs to be allocated with {@link IdService}
* class
* This annotation is needed for any ID field that needs to be allocated with {@link
* google.registry.persistence.transaction.TransactionManager#allocateId} class
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -0,0 +1,177 @@
// 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.
package google.registry.model.common;
import static com.google.api.client.util.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets;
import google.registry.model.Buildable;
import google.registry.model.CacheUtils;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.joda.time.DateTime;
@Entity
public class FeatureFlag extends ImmutableObject implements Buildable {
/**
* The current status of the feature the flag represents.
*
* <p>Currently, there is no enforced ordering of these status values, but that may change in the
* future should new statuses be added to this enum that require it.
*/
public enum FeatureStatus {
ACTIVE,
INACTIVE
}
/** The name of the flag/feature. */
@Id String featureName;
/** A map of times for each {@link FeatureStatus} the FeatureFlag should hold. */
@Column(nullable = false)
TimedTransitionProperty<FeatureStatus> status =
TimedTransitionProperty.withInitialValue(FeatureStatus.INACTIVE);
public static FeatureFlag get(String featureName) {
FeatureFlag maybeFeatureFlag = CACHE.get(featureName);
if (maybeFeatureFlag == null) {
throw new FeatureFlagNotFoundException(featureName);
} else {
return maybeFeatureFlag;
}
}
public static ImmutableSet<FeatureFlag> get(Set<String> featureNames) {
Map<String, FeatureFlag> featureFlags = CACHE.getAll(featureNames);
ImmutableSet<String> missingFlags =
Sets.difference(featureNames, featureFlags.keySet()).immutableCopy();
if (missingFlags.isEmpty()) {
return featureFlags.values().stream().collect(toImmutableSet());
} else {
throw new FeatureFlagNotFoundException(missingFlags);
}
}
/** A cache that loads the {@link FeatureFlag} for a given featureName. */
private static final LoadingCache<String, FeatureFlag> CACHE =
CacheUtils.newCacheBuilder(getSingletonCacheRefreshDuration())
.build(
new CacheLoader<>() {
@Override
public FeatureFlag load(final String featureName) {
return tm().reTransact(() -> tm().loadByKeyIfPresent(createVKey(featureName)))
.orElse(null);
}
@Override
public Map<? extends String, ? extends FeatureFlag> loadAll(
Set<? extends String> featureFlagNames) {
ImmutableMap<String, VKey<FeatureFlag>> keysMap =
featureFlagNames.stream()
.collect(
toImmutableMap(featureName -> featureName, FeatureFlag::createVKey));
Map<VKey<? extends FeatureFlag>, FeatureFlag> entities =
tm().reTransact(() -> tm().loadByKeysIfPresent(keysMap.values()));
return entities.values().stream()
.collect(toImmutableMap(flag -> flag.featureName, flag -> flag));
}
});
public static VKey<FeatureFlag> createVKey(String featureName) {
return VKey.create(FeatureFlag.class, featureName);
}
@Override
public VKey<FeatureFlag> createVKey() {
return createVKey(featureName);
}
public String getFeatureName() {
return featureName;
}
public TimedTransitionProperty<FeatureStatus> getStatusMap() {
return status;
}
public FeatureStatus getStatus(DateTime time) {
return status.getValueAtTime(time);
}
@Override
public FeatureFlag.Builder asBuilder() {
return new FeatureFlag.Builder(clone(this));
}
/** A builder for constructing {@link FeatureFlag} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<FeatureFlag> {
public Builder() {}
private Builder(FeatureFlag instance) {
super(instance);
}
@Override
public FeatureFlag build() {
checkArgument(
!Strings.isNullOrEmpty(getInstance().featureName),
"Feature name must not be null or empty");
getInstance().status.checkValidity();
return super.build();
}
public Builder setFeatureName(String featureName) {
checkState(getInstance().featureName == null, "Feature name can only be set once");
getInstance().featureName = featureName;
return this;
}
public Builder setStatus(ImmutableSortedMap<DateTime, FeatureStatus> statusMap) {
getInstance().status = TimedTransitionProperty.fromValueMap(statusMap);
return this;
}
}
/** Exception to throw when no FeatureFlag entity is found for given FeatureName string(s). */
public static class FeatureFlagNotFoundException extends RuntimeException {
FeatureFlagNotFoundException(ImmutableSet<String> featureNames) {
super("No feature flag object(s) found for " + Joiner.on(", ").join(featureNames));
}
FeatureFlagNotFoundException(String featureName) {
this(ImmutableSet.of(featureName));
}
}
}
@@ -15,7 +15,7 @@
package google.registry.model.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
@@ -70,7 +70,8 @@ public class GracePeriod extends GracePeriodBase {
(billingRecurrence != null) == GracePeriodStatus.AUTO_RENEW.equals(type),
"BillingRecurrences must be present on (and only on) autorenew grace periods");
GracePeriod instance = new GracePeriod();
instance.gracePeriodId = gracePeriodId == null ? allocateId() : gracePeriodId;
instance.gracePeriodId =
gracePeriodId == null ? tm().reTransact(tm()::allocateId) : gracePeriodId;
instance.type = checkArgumentNotNull(type);
instance.domainRepoId = checkArgumentNotNull(domainRepoId);
instance.expirationTime = checkArgumentNotNull(expirationTime);
@@ -198,7 +199,7 @@ public class GracePeriod extends GracePeriodBase {
static GracePeriodHistory createFrom(long historyRevisionId, GracePeriod gracePeriod) {
GracePeriodHistory instance = new GracePeriodHistory();
instance.gracePeriodHistoryRevisionId = allocateId();
instance.gracePeriodHistoryRevisionId = tm().reTransact(tm()::allocateId);
instance.domainHistoryRevisionId = historyRevisionId;
instance.gracePeriodId = gracePeriod.gracePeriodId;
instance.type = gracePeriod.type;
@@ -14,7 +14,7 @@
package google.registry.model.domain.secdns;
import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import google.registry.model.domain.DomainHistory;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
@@ -48,7 +48,7 @@ public class DomainDsDataHistory extends DomainDsDataBase {
instance.algorithm = dsData.getAlgorithm();
instance.digestType = dsData.getDigestType();
instance.digest = dsData.getDigest();
instance.dsDataHistoryRevisionId = allocateId();
instance.dsDataHistoryRevisionId = tm().reTransact(tm()::allocateId);
return instance;
}
@@ -62,6 +62,8 @@ import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.VKey;
import google.registry.util.CidrAddressBlock;
import google.registry.util.PasswordUtils;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.security.cert.CertificateParsingException;
import java.util.Comparator;
import java.util.List;
@@ -72,8 +74,6 @@ import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
@@ -41,9 +41,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.net.InternetDomainName;
import google.registry.model.Buildable;
import google.registry.model.CacheUtils;
@@ -204,10 +204,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
public static ImmutableSet<Tld> get(Set<String> tlds) {
Map<String, Tld> registries = CACHE.getAll(tlds);
ImmutableSet<String> missingRegistries =
registries.entrySet().stream()
.filter(e -> e.getValue() == null)
.map(Map.Entry::getKey)
.collect(toImmutableSet());
Sets.difference(tlds, registries.keySet()).immutableCopy();
if (missingRegistries.isEmpty()) {
return registries.values().stream().collect(toImmutableSet());
} else {
@@ -243,7 +240,8 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
tlds.stream().collect(toImmutableMap(tld -> tld, Tld::createVKey));
Map<VKey<? extends Tld>, Tld> entities =
tm().reTransact(() -> tm().loadByKeysIfPresent(keysMap.values()));
return Maps.transformEntries(keysMap, (k, v) -> entities.getOrDefault(v, null));
return entities.values().stream()
.collect(toImmutableMap(tld -> tld.tldStr, tld -> tld));
}
});
@@ -114,6 +114,7 @@ import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -192,6 +193,8 @@ interface RequestComponent {
ConsoleUiAction consoleUiAction();
ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction();
ConsoleUserDataAction consoleUserDataAction();
ConsoleDumDownloadAction consoleDumDownloadAction();
@@ -30,6 +30,7 @@ import google.registry.ui.server.console.ConsoleDomainListAction;
import google.registry.ui.server.console.ConsoleDumDownloadAction;
import google.registry.ui.server.console.ConsoleEppPasswordAction;
import google.registry.ui.server.console.ConsoleRegistryLockAction;
import google.registry.ui.server.console.ConsoleUpdateRegistrarAction;
import google.registry.ui.server.console.ConsoleUserDataAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.settings.ContactAction;
@@ -70,6 +71,8 @@ public interface FrontendRequestComponent {
ConsoleUiAction consoleUiAction();
ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction();
ConsoleUserDataAction consoleUserDataAction();
ConsoleDumDownloadAction consoleDumDownloadAction();
@@ -0,0 +1,34 @@
// 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.
package google.registry.persistence.converter;
import google.registry.model.common.FeatureFlag.FeatureStatus;
import javax.persistence.Converter;
/** JPA converter for {@link google.registry.model.common.FeatureFlag} status transitions. */
@Converter(autoApply = true)
public class FeatureFlagStatusConverter
extends TimedTransitionPropertyConverterBase<FeatureStatus> {
@Override
protected String convertValueToString(FeatureStatus value) {
return value.toString();
}
@Override
protected FeatureStatus convertStringToValue(String string) {
return FeatureStatus.valueOf(string);
}
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
package google.registry.model;
package google.registry.persistence.transaction;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -22,7 +22,7 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* Allocates a {@code long} to use as a {@code @Id}, (part) of the primary SQL key for an entity.
*/
public final class IdService {
final class IdService {
private IdService() {}
@@ -32,7 +32,7 @@ public final class IdService {
*
* <p>The generated IDs are project-wide unique.
*/
public static long allocateId() {
static long allocateId() {
return tm().transact(
() ->
(BigInteger)
@@ -56,6 +56,7 @@ import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
@@ -137,6 +138,12 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return transactionInfo.get().inTransaction;
}
@Override
public long allocateId() {
assertInTransaction();
return transactionInfo.get().idProvider.get();
}
@Override
public void assertInTransaction() {
if (!inTransaction()) {
@@ -210,7 +217,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
EntityTransaction txn = txnInfo.entityManager.getTransaction();
try {
txn.begin();
txnInfo.start(clock);
txnInfo.start(clock, readOnly ? ReplicaDbIdService::allocatedId : IdService::allocateId);
if (readOnly) {
getEntityManager().createNativeQuery("SET TRANSACTION READ ONLY").executeUpdate();
logger.atInfo().log("Using read-only SQL replica");
@@ -668,6 +675,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
EntityManager entityManager;
boolean inTransaction = false;
DateTime transactionTime;
Supplier<Long> idProvider;
// The set of entity objects that have been either persisted (via insert()) or merged (via
// put()/update()). If the entity manager returns these as a result of a find() or query
@@ -676,13 +684,15 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
Set<Object> objectsToSave = Collections.newSetFromMap(new IdentityHashMap<>());
/** Start a new transaction. */
private void start(Clock clock) {
private void start(Clock clock, Supplier<Long> idProvider) {
checkArgumentNotNull(clock);
inTransaction = true;
transactionTime = clock.nowUtc();
this.idProvider = idProvider;
}
private void clear() {
idProvider = null;
inTransaction = false;
transactionTime = null;
objectsToSave = Collections.newSetFromMap(new IdentityHashMap<>());
@@ -0,0 +1,38 @@
// 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.
//
package google.registry.persistence.transaction;
import java.util.concurrent.atomic.AtomicLong;
/**
* Provides {@code long} values for use as {@code id} by JPA model entities in (read-only)
* transactions in the replica database. Each id is only unique in the JVM instance.
*
* <p>The {@link IdService database sequence-based id service} cannot be used with the replica
* because id generation is a write operation.
*/
final class ReplicaDbIdService {
private ReplicaDbIdService() {}
private static final AtomicLong nextId = new AtomicLong(1);
/**
* Returns the next long value from a {@link AtomicLong}. Each id is unique in the JVM instance.
*/
static final long allocatedId() {
return nextId.getAndIncrement();
}
}
@@ -48,6 +48,15 @@ public interface TransactionManager {
*/
void assertInTransaction();
/**
* Returns a {@link long} value that can be used as {@code id} by a JPA model entity.
*
* <p>The returned value must be project-wide unique when transacting on the primary database
* instance, but only needs to be unique within a JVM instance when transacting on the replica
* instance.
*/
long allocateId();
/** Executes the work in a transaction and returns the result. */
<T> T transact(Callable<T> work);
@@ -26,9 +26,9 @@ import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.reporting.billing.BillingModule.InvoiceDirectoryPrefix;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.YearMonth;
/** Utility functions for sending emails involving monthly invoices. */
@@ -38,9 +38,9 @@ import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.Duration;
import org.joda.time.YearMonth;
import org.joda.time.format.DateTimeFormat;
@@ -42,13 +42,13 @@ import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import jakarta.mail.internet.InternetAddress;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
@@ -38,11 +38,11 @@ import google.registry.model.registrar.RegistrarPoc;
import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.util.EmailMessage;
import google.registry.util.Sleeper;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
@@ -16,11 +16,9 @@ package google.registry.storage.drive;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Children;
import com.google.api.services.drive.model.ChildList;
import com.google.api.services.drive.model.ChildReference;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.FileList;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
@@ -37,16 +35,19 @@ public class DriveConnection {
/** Drive client instance wrapped by this class. */
@Inject Drive drive;
@Inject public DriveConnection() {}
@Inject
public DriveConnection() {}
/**
* Creates a folder with the given parent.
*
* @return the folder id.
*/
public String createFolder(String title, String parentFolderId) throws IOException {
return drive.files()
.insert(createFileReference(title, GOOGLE_FOLDER, parentFolderId))
public String createFolder(String name, @Nullable String parentFolderId) throws IOException {
return drive
.files()
.create(createFileReference(name, GOOGLE_FOLDER, parentFolderId))
.execute()
.getId();
}
@@ -60,11 +61,12 @@ public class DriveConnection {
*
* @return the file id.
*/
public String createFile(String title, MediaType mimeType, String parentFolderId, byte[] bytes)
public String createFile(String name, MediaType mimeType, String parentFolderId, byte[] bytes)
throws IOException {
return drive.files()
.insert(
createFileReference(title, mimeType, parentFolderId),
return drive
.files()
.create(
createFileReference(name, mimeType, parentFolderId),
new ByteArrayContent(mimeType.toString(), bytes))
.execute()
.getId();
@@ -72,79 +74,79 @@ public class DriveConnection {
/**
* Creates a file with the given parent or updates the existing one if a file already exists with
* that same title and parent.
* that same name and parent.
*
* @throws IllegalStateException if multiple files with that name exist in the given folder.
* @throws IOException if communication with Google Drive fails for any reason.
* @return the file id.
*/
public String createOrUpdateFile(
String title, MediaType mimeType, String parentFolderId, byte[] bytes) throws IOException {
List<String> existingFiles = listFiles(parentFolderId, String.format("title = '%s'", title));
String name, MediaType mimeType, String parentFolderId, byte[] bytes) throws IOException {
List<String> existingFiles = listFiles(parentFolderId, String.format("name = '%s'", name));
if (existingFiles.size() > 1) {
throw new IllegalStateException(String.format(
"Could not update file '%s' in Drive folder id '%s' because multiple files with that "
+ "name already exist.",
title,
parentFolderId));
throw new IllegalStateException(
String.format(
"Could not update file '%s' in Drive folder id '%s' because multiple files with that "
+ "name already exist.",
name, parentFolderId));
}
return existingFiles.isEmpty()
? createFile(title, mimeType, parentFolderId, bytes)
: updateFile(existingFiles.get(0), title, mimeType, bytes);
? createFile(name, mimeType, parentFolderId, bytes)
: updateFile(existingFiles.getFirst(), name, mimeType, bytes);
}
/**
* Updates the file with the given id in place, setting the title, content, and mime type to the
* Updates the file with the given id in place, setting the name, content, and mime type to the
* newly specified values.
*
* @return the file id.
*/
public String updateFile(String fileId, String title, MediaType mimeType, byte[] bytes)
public String updateFile(String fileId, String name, MediaType mimeType, byte[] bytes)
throws IOException {
File file = new File().setTitle(title);
return drive.files()
File file = new File().setName(name);
return drive
.files()
.update(fileId, file, new ByteArrayContent(mimeType.toString(), bytes))
.execute()
.getId();
}
/**
* Returns a list of Drive file ids for all files in Google Drive in the folder with the
* specified id.
* Returns a list of Drive file ids for all files in Google Drive in the folder with the specified
* id.
*/
public List<String> listFiles(String parentFolderId) throws IOException {
return listFiles(parentFolderId, null);
}
/**
* Returns a list of Drive file ids for all files in Google Drive in the folder with the
* specified id and matching the given Drive query.
* Returns a list of Drive file ids for all files in Google Drive in the folder with the specified
* id and matching the given Drive query.
*
* @see <a href="https://developers.google.com/drive/web/search-parameters">The query format</a>
*/
public List<String> listFiles(String parentFolderId, String query) throws IOException {
ImmutableList.Builder<String> result = new ImmutableList.Builder<>();
Children.List req = drive.children().list(parentFolderId);
Files.List req = drive.files().list();
StringBuilder q = new StringBuilder(String.format("'%s' in parents", parentFolderId));
if (!Strings.isNullOrEmpty(query)) {
req.setQ(query);
q.append(String.format(" and %s", query));
}
req.setQ(q.toString());
do {
ChildList files = req.execute();
for (ChildReference child : files.getItems()) {
result.add(child.getId());
}
FileList files = req.execute();
files.getFiles().forEach(file -> result.add(file.getId()));
req.setPageToken(files.getNextPageToken());
} while (!Strings.isNullOrEmpty(req.getPageToken()));
return result.build();
}
/** Constructs an object representing a file (or folder) with a given title and parent. */
/** Constructs an object representing a file (or folder) with a given name and parent. */
private File createFileReference(
String title, MediaType mimeType, @Nullable String parentFolderId) {
String name, MediaType mimeType, @Nullable String parentFolderId) {
return new File()
.setTitle(title)
.setName(name)
.setMimeType(mimeType.toString())
.setParents(parentFolderId == null
? null
: ImmutableList.of(new ParentReference().setId(parentFolderId)));
.setParents(parentFolderId == null ? null : ImmutableList.of(parentFolderId));
}
}
@@ -15,7 +15,6 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.IdService.allocateId;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -119,7 +118,7 @@ public class UpdateRecurrenceCommand extends ConfirmingCommand {
domainsAndRecurrences.forEach(
(domain, existingRecurrence) -> {
// Make a new history ID to break the (recurrence, history, domain) circular dep chain
long newHistoryId = allocateId();
long newHistoryId = tm().allocateId();
HistoryEntryId newDomainHistoryId = new HistoryEntryId(domain.getRepoId(), newHistoryId);
BillingRecurrence endingNow =
existingRecurrence.asBuilder().setRecurrenceEndTime(now).build();
@@ -22,12 +22,12 @@ import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
/**
* Utility class for sending emails from the app.
@@ -44,12 +44,12 @@ import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletRequest;
import java.net.URISyntaxException;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.apache.http.client.utils.URIBuilder;
import org.joda.time.Duration;
@@ -38,7 +38,7 @@ import javax.inject.Inject;
@Action(
service = Action.Service.DEFAULT,
path = ConsoleEppPasswordAction.PATH,
path = ConsoleUpdateRegistrarAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
@@ -42,13 +42,13 @@ import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAcce
import google.registry.security.JsonResponseHelper;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.servlet.http.HttpServletRequest;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.apache.http.client.utils.URIBuilder;
import org.joda.time.Duration;
@@ -20,7 +20,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
import static google.registry.model.tld.Tlds.findTldForName;
import static google.registry.model.tld.Tlds.getTlds;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
@@ -67,7 +67,8 @@ public class DomainLookupCommand implements WhoisCommand {
// Include `getResponse` and `isBlockedByBsa` in one transaction to reduce latency.
// Must pass the exceptions outside to throw.
ResponseOrException result =
tm().transact(
replicaTm()
.transact(
() -> {
final Optional<WhoisResponse> response = getResponse(domainName, now);
if (response.isPresent()) {
@@ -16,7 +16,7 @@ package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
@@ -51,10 +51,12 @@ final class NameserverLookupByIpCommand implements WhoisCommand {
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
Iterable<Host> hostsFromDb;
hostsFromDb =
tm().transact(
replicaTm()
.transact(
() ->
// We cannot query @Convert-ed fields in HQL, so we must use native Postgres.
tm().getEntityManager()
replicaTm()
.getEntityManager()
/*
* Using array_operator <@ (contained-by) with gin index on inet_address.
* Without gin index, this is slightly slower than the alternative form of
@@ -56,6 +56,7 @@
<class>google.registry.model.contact.Contact</class>
<class>google.registry.model.domain.Domain</class>
<class>google.registry.model.domain.DomainHistory</class>
<class>google.registry.model.common.FeatureFlag</class>
<class>google.registry.model.domain.GracePeriod</class>
<class>google.registry.model.domain.GracePeriod$GracePeriodHistory</class>
<class>google.registry.model.domain.secdns.DomainDsData</class>
@@ -97,6 +98,7 @@
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.FeatureFlagStatusConverter</class>
<class>google.registry.persistence.converter.IdnTableEnumSetConverter</class>
<class>google.registry.persistence.converter.InetAddressSetConverter</class>
<class>google.registry.persistence.converter.LocalDateConverter</class>
@@ -44,10 +44,13 @@ import schemacrawler.schemacrawler.LoadOptionsBuilder;
import schemacrawler.schemacrawler.SchemaCrawlerOptions;
import schemacrawler.schemacrawler.SchemaCrawlerOptionsBuilder;
import schemacrawler.schemacrawler.SchemaInfoLevelBuilder;
import schemacrawler.tools.command.text.diagram.options.DiagramOutputFormat;
import schemacrawler.tools.executable.SchemaCrawlerExecutable;
import schemacrawler.tools.integration.diagram.DiagramOutputFormat;
import schemacrawler.tools.options.OutputOptions;
import schemacrawler.tools.options.OutputOptionsBuilder;
import us.fatehi.utility.datasource.DatabaseConnectionSource;
import us.fatehi.utility.datasource.DatabaseConnectionSources;
import us.fatehi.utility.datasource.MultiUseUserCredentials;
/** Command to generate ER diagrams for SQL schema. */
@Parameters(separators = " =", commandDescription = "Generate ER diagrams for SQL schema.")
@@ -111,10 +114,12 @@ public class GenerateSqlErDiagramCommand implements Command {
Connection conn = getConnection(postgresContainer);
initDb(conn);
if (diagramType == ALL || diagramType == FULL) {
improveDiagramHtml(generateErDiagram(conn, FULL_DIAGRAM_COMMAND, FULL_DIAGRAM_FILE_NAME));
improveDiagramHtml(
generateErDiagram(postgresContainer, FULL_DIAGRAM_COMMAND, FULL_DIAGRAM_FILE_NAME));
}
if (diagramType == ALL || diagramType == BRIEF) {
improveDiagramHtml(generateErDiagram(conn, BRIEF_DIAGRAM_COMMAND, BRIEF_DIAGRAM_FILE_NAME));
improveDiagramHtml(
generateErDiagram(postgresContainer, BRIEF_DIAGRAM_COMMAND, BRIEF_DIAGRAM_FILE_NAME));
}
}
}
@@ -165,7 +170,8 @@ public class GenerateSqlErDiagramCommand implements Command {
}
}
private Path generateErDiagram(Connection connection, String command, String fileName) {
private Path generateErDiagram(
PostgreSQLContainer<?> container, String command, String fileName) {
Path outputFile = outDir.resolve(fileName);
LoadOptionsBuilder loadOptionsBuilder =
@@ -179,7 +185,11 @@ public class GenerateSqlErDiagramCommand implements Command {
SchemaCrawlerExecutable executable = new SchemaCrawlerExecutable(command);
executable.setSchemaCrawlerOptions(options);
executable.setOutputOptions(outputOptions);
executable.setConnection(connection);
DatabaseConnectionSource connection =
DatabaseConnectionSources.newDatabaseConnectionSource(
container.getJdbcUrl(),
new MultiUseUserCredentials(container.getUsername(), container.getPassword()));
executable.setDataSource(connection);
try {
executable.execute();
} catch (Exception e) {
@@ -50,8 +50,8 @@ import google.registry.testing.FakeResponse;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.StringGenerator.Alphabets;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
@@ -43,10 +43,10 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.util.SelfSignedCaCertificate;
import jakarta.mail.internet.InternetAddress;
import java.security.cert.X509Certificate;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,7 +27,7 @@ import google.registry.groups.GmailClient;
import google.registry.request.Response;
import google.registry.testing.FakeClock;
import google.registry.util.EmailMessage;
import javax.mail.internet.InternetAddress;
import jakarta.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
@@ -56,9 +56,9 @@ import google.registry.request.Response;
import google.registry.testing.FakeClock;
import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import java.util.stream.Stream;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
@@ -64,8 +64,8 @@ import google.registry.testing.FakeLockHandler;
import google.registry.testing.FakeResponse;
import google.registry.testing.Lazies;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.InternetAddress;
import java.util.Set;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
@@ -0,0 +1,89 @@
// 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.
package google.registry.flows;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newTld;
import static org.junit.jupiter.api.Assertions.assertThrows;
import dagger.Component;
import google.registry.flows.FlowComponent.FlowComponentModule;
import google.registry.model.eppinput.EppInput;
import google.registry.persistence.transaction.DatabaseException;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.testing.EppLoader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link FlowModule}. */
public class FlowModuleTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private EppInput getEppInput(String eppInputXmlFilename) throws EppException {
return new EppLoader(this, eppInputXmlFilename).getEpp();
}
@Test
void givenMutatingFlow_thenPrimaryTmIsUsed() throws EppException {
String eppInputXmlFilename = "domain_create.xml";
FlowModule flowModule =
new FlowModule.Builder().setEppInput(getEppInput(eppInputXmlFilename)).build();
JpaTransactionManager tm =
DaggerFlowModuleTest_FlowModuleTestComponent.builder()
.flowModule(flowModule)
.build()
.jpaTm();
assertThat(tm).isEqualTo(tm());
tm.transact(() -> tm.put(newTld("app", "ROID")));
}
@Test
void givenNonMutatingFlow_thenReplicaTmIsUsed() throws EppException {
String eppInputXmlFilename = "domain_info.xml";
FlowModule flowModule =
new FlowModule.Builder().setEppInput(getEppInput(eppInputXmlFilename)).build();
JpaTransactionManager tm =
DaggerFlowModuleTest_FlowModuleTestComponent.builder()
.flowModule(flowModule)
.build()
.jpaTm();
assertThat(tm).isEqualTo(replicaTm());
assertThat(
assertThrows(
DatabaseException.class, () -> tm.transact(() -> tm.put(newTld("app", "ROID")))))
.hasMessageThat()
.contains("cannot execute INSERT in a read-only transaction");
}
@FlowScope
@Component(modules = {FlowModule.class, FlowComponentModule.class})
public interface FlowModuleTestComponent {
JpaTransactionManager jpaTm();
@Component.Builder
interface Builder {
Builder flowModule(FlowModule flowModule);
FlowModuleTestComponent build();
}
}
}
@@ -111,6 +111,7 @@ class FlowRunnerTest {
new StatelessRequestSessionMetadata("TheRegistrar", ImmutableSet.of());
flowRunner.trid = Trid.create("client-123", "server-456");
flowRunner.flowReporter = mock(FlowReporter.class);
flowRunner.jpaTransactionManager = tm();
}
@Test
@@ -87,6 +87,7 @@ import google.registry.model.tld.Tld.TldState;
import google.registry.model.tld.label.ReservedList;
import google.registry.testing.DatabaseHelper;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -160,6 +161,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testSuccess_bsaBlocked_otherwiseAvailable_blocked() throws Exception {
persistResource(
Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("example1");
doCheckTest(
create(false, "example1.tld", "Blocked by a GlobalBlock service"),
@@ -169,6 +172,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testSuccess_bsaBlocked_alsoRegistered_registered() throws Exception {
persistResource(
Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("example1");
persistActiveDomain("example1.tld");
doCheckTest(
@@ -179,6 +184,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testSuccess_bsaBlocked_alsoReserved_reserved() throws Exception {
persistResource(
Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("reserved");
persistBsaLabel("allowedinsunrise");
setEppInput("domain_check_one_tld_reserved.xml");
@@ -191,6 +198,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testSuccess_bsaBlocked_createAllowedWithToken() throws Exception {
persistResource(
Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("example1");
setEppInput("domain_check_allocationtoken.xml");
persistResource(
@@ -208,6 +217,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testSuccess_bsaBlocked_withIrrelevantTokenType() throws Exception {
persistResource(
Tld.get("tld").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("example1");
setEppInput("domain_check_allocationtoken.xml");
persistResource(
@@ -223,6 +234,19 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
create(false, "specificuse.tld", "Reserved; alloc. token required"));
}
@Test
void testSuccess_bsaBlocked_onlyInEnrolledTlds() throws Exception {
setEppInput("domain_check.xml");
createTlds("com", "net", "org");
persistResource(
Tld.get("com").asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build());
persistBsaLabel("example");
doCheckTest(
create(false, "example.com", "Blocked by a GlobalBlock service"),
create(true, "example.net", null),
create(true, "example.org", null));
}
@Test
void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_check_no_cltrid.xml");
@@ -39,12 +39,12 @@ import google.registry.util.EmailMessage;
import google.registry.util.EmailMessage.Attachment;
import google.registry.util.Retrier;
import google.registry.util.SystemSleeper;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.Part;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import java.io.OutputStream;
import javax.mail.Message.RecipientType;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -0,0 +1,207 @@
// 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.
package google.registry.model.common;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.EntityTestCase;
import google.registry.model.common.FeatureFlag.FeatureFlagNotFoundException;
import google.registry.model.common.FeatureFlag.FeatureStatus;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FeatureFlag}. */
public class FeatureFlagTest extends EntityTestCase {
public FeatureFlagTest() {
super(JpaEntityCoverageCheck.ENABLED);
}
@Test
void testSuccess_persistence() {
FeatureFlag featureFlag =
new FeatureFlag.Builder()
.setFeatureName("testFlag")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
FeatureFlag flagFromDb = loadByEntity(featureFlag);
assertThat(featureFlag).isEqualTo(flagFromDb);
}
@Test
void testSuccess_getSingleFlag() {
FeatureFlag featureFlag =
new FeatureFlag.Builder()
.setFeatureName("testFlag")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
assertThat(FeatureFlag.get("testFlag")).isEqualTo(featureFlag);
}
@Test
void testSuccess_getMultipleFlags() {
FeatureFlag featureFlag1 =
persistResource(
new FeatureFlag.Builder()
.setFeatureName("testFlag1")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build())
.build());
FeatureFlag featureFlag2 =
persistResource(
new FeatureFlag.Builder()
.setFeatureName("testFlag2")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlag featureFlag3 =
persistResource(
new FeatureFlag.Builder()
.setFeatureName("testFlag3")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.build())
.build());
ImmutableSet<FeatureFlag> featureFlags =
FeatureFlag.get(ImmutableSet.of("testFlag1", "testFlag2", "testFlag3"));
assertThat(featureFlags.size()).isEqualTo(3);
assertThat(featureFlags).containsExactly(featureFlag1, featureFlag2, featureFlag3);
}
@Test
void testFailure_getMultipleFlagsOneMissing() {
persistResource(
new FeatureFlag.Builder()
.setFeatureName("testFlag1")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build())
.build());
persistResource(
new FeatureFlag.Builder()
.setFeatureName("testFlag2")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlagNotFoundException thrown =
assertThrows(
FeatureFlagNotFoundException.class,
() -> FeatureFlag.get(ImmutableSet.of("missingFlag", "testFlag1", "testFlag2")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("No feature flag object(s) found for missingFlag");
}
@Test
void testFailure_featureFlagNotPresent() {
FeatureFlagNotFoundException thrown =
assertThrows(FeatureFlagNotFoundException.class, () -> FeatureFlag.get("fakeFlag"));
assertThat(thrown).hasMessageThat().isEqualTo("No feature flag object(s) found for fakeFlag");
}
@Test
void testFailure_resetFeatureName() {
FeatureFlag featureFlag =
new FeatureFlag.Builder()
.setFeatureName("testFlag")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build())
.build();
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() -> featureFlag.asBuilder().setFeatureName("differentName"));
assertThat(thrown).hasMessageThat().isEqualTo("Feature name can only be set once");
}
@Test
void testFailure_nullFeatureName() {
FeatureFlag.Builder featureFlagBuilder =
new FeatureFlag.Builder()
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build());
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> featureFlagBuilder.build());
assertThat(thrown).hasMessageThat().isEqualTo("Feature name must not be null or empty");
}
@Test
void testFailure_emptyFeatureName() {
FeatureFlag.Builder featureFlagBuilder =
new FeatureFlag.Builder()
.setFeatureName("")
.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build());
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> featureFlagBuilder.build());
assertThat(thrown).hasMessageThat().isEqualTo("Feature name must not be null or empty");
}
@Test
void testFailure_invalidStatusMap() {
FeatureFlag.Builder featureFlagBuilder = new FeatureFlag.Builder().setFeatureName("testFlag");
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
featureFlagBuilder.setStatus(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.build()));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Must provide transition entry for the start of time (Unix Epoch)");
}
}
@@ -325,6 +325,16 @@ public final class TldTest extends EntityTestCase {
.values());
}
@Test
void testFailure_testGetAllMissingTld() {
createTld("foo");
TldNotFoundException thrown =
assertThrows(
TldNotFoundException.class,
() -> assertThat(Tld.get(ImmutableSet.of("foo", "tld", "missing"))));
assertThat(thrown).hasMessageThat().isEqualTo("No TLD object(s) found for missing");
}
@Test
void testSetReservedLists() {
ReservedList rl5 =
@@ -26,9 +26,9 @@ import com.google.common.net.MediaType;
import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.util.EmailMessage;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import org.joda.time.YearMonth;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -109,7 +109,7 @@ class BillingEmailUtilsTest {
assertThat(thrown)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("javax.mail.MessagingException: expected");
.isEqualTo("jakarta.mail.MessagingException: expected");
// Verify we sent an e-mail alert
verify(gmailClient, times(2)).sendEmail(contentCaptor.capture());
validateAlertMessage(contentCaptor.getValue(), "Emailing invoice failed due to expected");
@@ -35,8 +35,8 @@ import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.YearMonth;
@@ -44,10 +44,10 @@ import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import jakarta.mail.internet.InternetAddress;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -44,11 +44,11 @@ import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.testing.DatabaseHelper;
import google.registry.util.EmailMessage;
import google.registry.util.Sleeper;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.junit.jupiter.api.BeforeEach;
@@ -23,6 +23,7 @@ import google.registry.bsa.persistence.BsaUnblockableDomainTest;
import google.registry.model.billing.BillingBaseTest;
import google.registry.model.common.CursorTest;
import google.registry.model.common.DnsRefreshRequestTest;
import google.registry.model.common.FeatureFlagTest;
import google.registry.model.console.ConsoleEppActionHistoryTest;
import google.registry.model.console.RegistrarPocUpdateHistoryTest;
import google.registry.model.console.RegistrarUpdateHistoryTest;
@@ -103,6 +104,7 @@ import org.junit.runner.RunWith;
DnsRefreshRequestTest.class,
DomainSqlTest.class,
DomainHistoryTest.class,
FeatureFlagTest.class,
HostHistoryTest.class,
LockTest.class,
PollMessageTest.class,
@@ -18,7 +18,6 @@ import static com.google.common.io.ByteStreams.toByteArray;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -28,12 +27,9 @@ import static org.mockito.Mockito.when;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Children;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.ChildList;
import com.google.api.services.drive.model.ChildReference;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.FileList;
import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import java.util.Arrays;
@@ -47,15 +43,13 @@ class DriveConnectionTest {
private final Drive drive = mock(Drive.class);
private final Files files = mock(Files.class);
private final Children children = mock(Children.class);
private final Files.Insert insert = mock(Files.Insert.class);
private final Files.Create create = mock(Files.Create.class);
private final Files.Update update = mock(Files.Update.class);
private final Children.List childrenList = mock(Children.List.class);
private final Files.List filesList = mock(Files.List.class);
private static final byte[] DATA = {1, 2, 3};
ChildList childList;
private DriveConnection driveConnection;
private List<String> allChildren;
private List<String> allFiles;
private ArgumentMatcher<ByteArrayContent> hasByteArrayContent(final byte[] data) {
return arg -> {
@@ -72,154 +66,143 @@ class DriveConnectionTest {
driveConnection = new DriveConnection();
driveConnection.drive = drive;
when(drive.files()).thenReturn(files);
when(drive.children()).thenReturn(children);
when(insert.execute()).thenReturn(new File().setId("id"));
when(create.execute()).thenReturn(new File().setId("id"));
when(update.execute()).thenReturn(new File().setId("id"));
// Mocking required for listFiles.
ChildReference child1 = new ChildReference().setId("child1");
ChildReference child2 = new ChildReference().setId("child2");
ChildReference child3 = new ChildReference().setId("child3");
ChildReference child4 = new ChildReference().setId("child4");
List<ChildReference> children1 = ImmutableList.of(child1, child2);
List<ChildReference> children2 = ImmutableList.of(child3, child4);
allChildren = ImmutableList.of(child1.getId(), child2.getId(), child3.getId(), child4.getId());
ChildList childList1 = new ChildList();
childList1.setItems(children1);
childList1.setNextPageToken("page2");
ChildList childList2 = new ChildList();
childList2.setItems(children2);
childList2.setNextPageToken(null);
when(childrenList.execute()).thenReturn(childList1, childList2);
when(childrenList.setQ(anyString())).thenReturn(childrenList);
when(childrenList.getPageToken()).thenCallRealMethod();
when(childrenList.setPageToken(any())).thenCallRealMethod();
when(children.list("driveFolderId")).thenReturn(childrenList);
File file1 = new File().setId("file1");
File file2 = new File().setId("file2");
File file3 = new File().setId("file2");
File file4 = new File().setId("file4");
List<File> files1 = ImmutableList.of(file1, file2);
List<File> files2 = ImmutableList.of(file3, file4);
allFiles = ImmutableList.of(file1.getId(), file2.getId(), file3.getId(), file4.getId());
FileList fileList1 = new FileList();
fileList1.setFiles(files1);
fileList1.setNextPageToken("page2");
FileList fileList2 = new FileList();
fileList2.setFiles(files2);
fileList2.setNextPageToken(null);
when(filesList.execute()).thenReturn(fileList1, fileList2);
when(filesList.getPageToken()).thenCallRealMethod();
when(filesList.setPageToken(any())).thenCallRealMethod();
when(files.list()).thenReturn(filesList);
}
@Test
void testCreateFileAtRoot() throws Exception {
when(files.insert(
eq(new File().setTitle("title").setMimeType("image/gif")),
when(files.create(
eq(new File().setName("name").setMimeType("image/gif")),
argThat(hasByteArrayContent(DATA))))
.thenReturn(insert);
assertThat(driveConnection.createFile("title", MediaType.GIF, null, DATA)).isEqualTo("id");
.thenReturn(create);
assertThat(driveConnection.createFile("name", MediaType.GIF, null, DATA)).isEqualTo("id");
}
@Test
void testCreateFileInFolder() throws Exception {
when(files.insert(
when(files.create(
eq(
new File()
.setTitle("title")
.setName("name")
.setMimeType("image/gif")
.setParents(ImmutableList.of(new ParentReference().setId("parent")))),
.setParents(ImmutableList.of("parent"))),
argThat(hasByteArrayContent(DATA))))
.thenReturn(insert);
assertThat(driveConnection.createFile("title", MediaType.GIF, "parent", DATA)).isEqualTo("id");
.thenReturn(create);
assertThat(driveConnection.createFile("name", MediaType.GIF, "parent", DATA)).isEqualTo("id");
}
@Test
void testCreateFolderAtRoot() throws Exception {
when(files.insert(
new File().setTitle("title").setMimeType("application/vnd.google-apps.folder")))
.thenReturn(insert);
assertThat(driveConnection.createFolder("title", null)).isEqualTo("id");
when(files.create(new File().setName("name").setMimeType("application/vnd.google-apps.folder")))
.thenReturn(create);
assertThat(driveConnection.createFolder("name", null)).isEqualTo("id");
}
@Test
void testCreateFolderInFolder() throws Exception {
when(files.insert(
when(files.create(
new File()
.setTitle("title")
.setName("name")
.setMimeType("application/vnd.google-apps.folder")
.setParents(ImmutableList.of(new ParentReference().setId("parent")))))
.thenReturn(insert);
assertThat(driveConnection.createFolder("title", "parent")).isEqualTo("id");
.setParents(ImmutableList.of("parent"))))
.thenReturn(create);
assertThat(driveConnection.createFolder("name", "parent")).isEqualTo("id");
}
@Test
void testListFiles_noQueryWithPagination() throws Exception {
assertThat(driveConnection.listFiles("driveFolderId")).containsExactlyElementsIn(allChildren);
verify(childrenList).setPageToken("page2");
verify(childrenList).setPageToken(null);
verify(childrenList, times(0)).setQ(anyString());
verify(childrenList, times(2)).getPageToken();
assertThat(driveConnection.listFiles("driveFolderId")).containsExactlyElementsIn(allFiles);
verify(filesList).setPageToken("page2");
verify(filesList).setPageToken(null);
verify(filesList, times(1)).setQ("'driveFolderId' in parents");
verify(filesList, times(2)).getPageToken();
}
@Test
void testListFiles_withQueryAndPagination() throws Exception {
assertThat(driveConnection.listFiles("driveFolderId", "sampleQuery"))
.containsExactlyElementsIn(allChildren);
verify(childrenList).setPageToken("page2");
verify(childrenList).setPageToken(null);
verify(childrenList, times(1)).setQ("sampleQuery");
verify(childrenList, times(2)).getPageToken();
.containsExactlyElementsIn(allFiles);
verify(filesList).setPageToken("page2");
verify(filesList).setPageToken(null);
verify(filesList, times(1)).setQ("'driveFolderId' in parents and sampleQuery");
verify(filesList, times(2)).getPageToken();
}
@Test
void testCreateOrUpdateFile_succeedsForNewFile() throws Exception {
when(files.insert(
when(files.create(
eq(
new File()
.setTitle("title")
.setName("name")
.setMimeType("video/webm")
.setParents(ImmutableList.of(new ParentReference().setId("driveFolderId")))),
.setParents(ImmutableList.of("driveFolderId"))),
argThat(hasByteArrayContent(DATA))))
.thenReturn(insert);
ChildList emptyChildList = new ChildList().setItems(ImmutableList.of()).setNextPageToken(null);
when(childrenList.execute()).thenReturn(emptyChildList);
.thenReturn(create);
FileList emptyFileList = new FileList().setFiles(ImmutableList.of()).setNextPageToken(null);
when(filesList.execute()).thenReturn(emptyFileList);
assertThat(
driveConnection.createOrUpdateFile(
"title", MediaType.WEBM_VIDEO, "driveFolderId", DATA))
driveConnection.createOrUpdateFile("name", MediaType.WEBM_VIDEO, "driveFolderId", DATA))
.isEqualTo("id");
}
@Test
void testCreateOrUpdateFile_succeedsForUpdatingFile() throws Exception {
when(files.update(
eq("id"), eq(new File().setTitle("title")), argThat(hasByteArrayContent(DATA))))
when(files.update(eq("id"), eq(new File().setName("name")), argThat(hasByteArrayContent(DATA))))
.thenReturn(update);
ChildList childList =
new ChildList()
.setItems(ImmutableList.of(new ChildReference().setId("id")))
.setNextPageToken(null);
when(childrenList.execute()).thenReturn(childList);
FileList fileList =
new FileList().setFiles(ImmutableList.of(new File().setId("id"))).setNextPageToken(null);
when(filesList.execute()).thenReturn(fileList);
assertThat(
driveConnection.createOrUpdateFile(
"title", MediaType.WEBM_VIDEO, "driveFolderId", DATA))
driveConnection.createOrUpdateFile("name", MediaType.WEBM_VIDEO, "driveFolderId", DATA))
.isEqualTo("id");
}
@Test
void testCreateOrUpdateFile_throwsExceptionWhenMultipleFilesWithNameAlreadyExist()
throws Exception {
ChildList childList =
new ChildList()
.setItems(
ImmutableList.of(
new ChildReference().setId("id1"), new ChildReference().setId("id2")))
FileList fileList =
new FileList()
.setFiles(ImmutableList.of(new File().setId("id1"), new File().setId("id2")))
.setNextPageToken(null);
when(childrenList.execute()).thenReturn(childList);
when(filesList.execute()).thenReturn(fileList);
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() ->
driveConnection.createOrUpdateFile(
"title", MediaType.WEBM_VIDEO, "driveFolderId", DATA));
"name", MediaType.WEBM_VIDEO, "driveFolderId", DATA));
assertThat(thrown)
.hasMessageThat()
.contains(
"Could not update file 'title' in Drive folder id 'driveFolderId' "
"Could not update file 'name' in Drive folder id 'driveFolderId' "
+ "because multiple files with that name already exist.");
}
@Test
void testUpdateFile_succeeds() throws Exception {
when(files.update(
eq("id"), eq(new File().setTitle("title")), argThat(hasByteArrayContent(DATA))))
when(files.update(eq("id"), eq(new File().setName("name")), argThat(hasByteArrayContent(DATA))))
.thenReturn(update);
assertThat(driveConnection.updateFile("id", "title", MediaType.WEBM_VIDEO, DATA))
assertThat(driveConnection.updateFile("id", "name", MediaType.WEBM_VIDEO, DATA))
.isEqualTo("id");
}
}
@@ -28,7 +28,6 @@ import static google.registry.config.RegistryConfig.getContactAndHostRoidSuffix;
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.IdService.allocateId;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
import static google.registry.model.ResourceTransferUtils.createTransferResponse;
@@ -393,7 +392,7 @@ public final class DatabaseHelper {
// prevent breaking some hard-coded flow tests. IDs in tests are allocated in a strictly
// increasing sequence, if we don't pad out the ID here, we would have to renumber hundreds of
// unit tests.
allocateId();
tm().reTransact(tm()::allocateId);
PremiumListDao.save(premiumList);
maybeAdvanceClock();
return premiumList;
@@ -963,12 +962,12 @@ public final class DatabaseHelper {
/** Returns a newly allocated, globally unique domain repoId of the format HEX-TLD. */
public static String generateNewDomainRoid(String tld) {
return createDomainRepoId(allocateId(), tld);
return createDomainRepoId(tm().reTransact(tm()::allocateId), tld);
}
/** Returns a newly allocated, globally unique contact/host repoId of the format HEX_TLD-ROID. */
public static String generateNewContactHostRoid() {
return createRepoId(allocateId(), getContactAndHostRoidSuffix());
return createRepoId(tm().reTransact(tm()::allocateId), getContactAndHostRoidSuffix());
}
/** Persists an object in the DB for tests. */
@@ -24,9 +24,9 @@ import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import google.registry.groups.GmailClient;
import google.registry.util.EmailMessage;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.util.Optional;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -47,12 +47,12 @@ import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordDat
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.EmailMessage;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Optional;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -54,9 +54,9 @@ import google.registry.tools.DomainLockUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import google.registry.util.StringGenerator;
import jakarta.mail.internet.InternetAddress;
import java.io.IOException;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
@@ -46,12 +46,12 @@ import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Optional;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@@ -99,7 +99,7 @@ class ConsoleUpdateRegistrarActionTest {
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
@Test
void testSuccess__updatesRegistrar() throws IOException {
void testSuccess_updatesRegistrar() throws IOException {
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
@@ -109,7 +109,7 @@ class ConsoleUpdateRegistrarActionTest {
}
@Test
void testFails__missingWhoisContact() throws IOException {
void testFails_missingWhoisContact() throws IOException {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
@@ -119,7 +119,7 @@ class ConsoleUpdateRegistrarActionTest {
}
@Test
void testSuccess__presentWhoisContact() throws IOException {
void testSuccess_presentWhoisContact() throws IOException {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
RegistrarPoc contact =
new RegistrarPoc.Builder()
@@ -143,7 +143,7 @@ class ConsoleUpdateRegistrarActionTest {
}
@Test
void testSuccess__sendsEmail() throws AddressException, IOException {
void testSuccess_sendsEmail() throws AddressException, IOException {
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))

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