diff --git a/.commitlintrc.yml b/.commitlintrc.yml new file mode 100644 index 0000000..a1e61d9 --- /dev/null +++ b/.commitlintrc.yml @@ -0,0 +1,54 @@ +--- +extends: '@commitlint/config-conventional' + +rules: + # See: https://commitlint.js.org/reference/rules.html + # + # Rules are made up by a name and a configuration array. The configuration + # array contains: + # + # * Severity [0..2]: 0 disable rule, 1 warning if violated, or 2 error if + # violated + # * Applicability [always|never]: never inverts the rule + # * Value: value to use for this rule (if applicable) + # + # Run `npx commitlint --print-config` to see the current setting for all rules. + + header-max-length: [2, always, 100] # Header can not exceed 100 chars + + type-case: [2, always, lower-case] # Type must be lower case + type-empty: [2, never] # Type must not be empty + + # Supported conventional commit types + type-enum: [2, always, [build, ci, chore, docs, feat, fix, perf, refactor, revert, style, test]] + + scope-case: [2, always, lower-case] # Scope must be lower case + + # Error if subject is one of these cases (encourages lower-case) + subject-case: [2, never, [sentence-case, start-case, pascal-case, upper-case]] + subject-empty: [2, never] # Subject must not be empty + subject-full-stop: [2, never, "."] # Subject must not end with a period + + body-leading-blank: [2, always] # Body must have a blank line before it + body-max-line-length: [2, always, 100] # Body lines can not exceed 100 chars + + footer-leading-blank: [2, always] # Footer must have a blank line before it + footer-max-line-length: [2, always, 100] # Footer lines can not exceed 100 chars + + # ------------------------------------------------------------ + # BREAKING CHANGES — guidance (informational; not enforced): + # + # How to mark a breaking change (either or both): + # 1) Put "!" in the header after the type or scope, e.g.: + # feat!: drop support for node 14 + # refactor(auth)!: remove legacy token flow + # + # 2) Add a footer that starts with: + # BREAKING CHANGE: + # Follow with impact/migration details, each line ≤ 100 chars. + # + # This config already allows both patterns via @commitlint/config-conventional. + # Note: commitlint cannot (in YAML) *require* a BREAKING CHANGE footer only + # when "!" is used. If you need that kind of conditional enforcement, use a + # JS config (*.cjs) with a custom rule. + # ------------------------------------------------------------ diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1773c3b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,30 @@ +# GitHub Copilot Instructions + +This repository implements Bottin, a NIP-05 registry service for Nostr. When using GitHub Copilot, keep the following guidelines in mind: + +## Commit Messages + +- Use Conventional Commits as defined in [.commitlintrc.yml](../.commitlintrc.yml). +- Allowed types: `build`, `ci`, `chore`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`. +- Header max 100 characters, lowercase type and scope. +- Example: `feat(api): add domain verification endpoint` + +## Code Guidelines + +- Maintain Java 21 compatibility and update `pom.xml` for new dependencies. +- Use Spring Boot conventions for controllers, services, and repositories. +- Remove unused imports. +- Run `mvn -q verify` before committing code. + +## Protocol + +- Follow the [NIP-05 specification](https://github.com/nostr-protocol/nips/blob/master/05.md) for identity verification endpoints. +- The `/.well-known/nostr.json` endpoint must return valid NIP-05 responses. + +## Pull Requests + +- Ensure pull requests include a clear description and test results. +- Reference related issues using `Closes #123` when applicable. +- Document new features in the README or related docs. + +These instructions help Copilot produce code that respects the repository's conventions and Nostr protocol requirements. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5de893e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 + +# Private registries used by Dependabot +registries: + maven-releases: + type: maven-repository + url: https://maven.398ja.xyz/releases + username: ${{secrets.MVN_USER}} + password: ${{secrets.MVN_PASSWORD}} + maven-snapshots: + type: maven-repository + url: https://maven.398ja.xyz/snapshots + username: ${{secrets.MVN_USER}} + password: ${{secrets.MVN_PASSWORD}} + +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + target-branch: "develop" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + target-branch: "develop" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..2fb9275 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +## Summary + + +## What changed? + + +## Breaking changes +- [ ] BREAKING: this change introduces breaking API or behavior + +## Review focus + + +## Checklist +- [ ] Tests added or updated +- [ ] `mvn -q verify` passes +- [ ] Documentation updated (README, docs, etc.) +- [ ] No unused imports \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..28a4e83 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + - name: Build with Maven + run: mvn -q verify + - name: Upload surefire reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: surefire-reports + path: '**/target/surefire-reports' + if-no-files-found: ignore + - name: Upload JaCoCo coverage + if: always() + uses: actions/upload-artifact@v4 + with: + name: jacoco-exec + path: '**/target/jacoco.exec' + if-no-files-found: ignore + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: '**/target/site/jacoco/jacoco.xml' + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: '**/target/surefire-reports/*.xml' diff --git a/.github/workflows/enforce_conventional_commits.yml b/.github/workflows/enforce_conventional_commits.yml new file mode 100644 index 0000000..89dc3bd --- /dev/null +++ b/.github/workflows/enforce_conventional_commits.yml @@ -0,0 +1,29 @@ +name: Conventional Commits + +permissions: + contents: read + pull-requests: read + +on: + pull_request: + branches: + - main + - develop + +jobs: + commit-lint: + name: Verify Conventional Commits + if: (github.event_name == 'pull_request' && !startsWith(github.event.pull_request.head.ref, 'release-please--')) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Check Commit Messages + uses: wagoid/commitlint-github-action@v6 + with: + configFile: .commitlintrc.yml + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/google-java-format.yml b/.github/workflows/google-java-format.yml new file mode 100644 index 0000000..9c31872 --- /dev/null +++ b/.github/workflows/google-java-format.yml @@ -0,0 +1,25 @@ +name: Format + +on: + pull_request: + branches: + - main + +permissions: + contents: write + +jobs: + + formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + - uses: axel-op/googlejavaformat-action@v4 + with: + args: "--replace" + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml new file mode 100644 index 0000000..82fee59 --- /dev/null +++ b/.github/workflows/qodana.yml @@ -0,0 +1,28 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + - develop + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/Dockerfile.admin b/Dockerfile.admin index d94193b..96e1928 100644 --- a/Dockerfile.admin +++ b/Dockerfile.admin @@ -34,8 +34,8 @@ WORKDIR /app RUN addgroup -g 1000 bottin && \ adduser -u 1000 -G bottin -s /bin/sh -D bottin -# Copy the built JAR -COPY --from=builder /app/bottin-admin-ui/target/bottin-admin-ui-*.jar app.jar +# Copy the built JAR (use the exec jar which is the repackaged Spring Boot jar) +COPY --from=builder /app/bottin-admin-ui/target/bottin-admin-ui-*-exec.jar app.jar # Set ownership RUN chown -R bottin:bottin /app diff --git a/Dockerfile.web b/Dockerfile.web index 8bac97c..5fa2fa6 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -34,8 +34,8 @@ WORKDIR /app RUN addgroup -g 1000 bottin && \ adduser -u 1000 -G bottin -s /bin/sh -D bottin -# Copy the built JAR -COPY --from=builder /app/bottin-web/target/bottin-web-*.jar app.jar +# Copy the built JAR (use the exec jar which is the repackaged Spring Boot jar) +COPY --from=builder /app/bottin-web/target/bottin-web-*-exec.jar app.jar # Set ownership RUN chown -R bottin:bottin /app diff --git a/README.md b/README.md index dc31a3c..1497783 100644 --- a/README.md +++ b/README.md @@ -53,35 +53,15 @@ mvn spring-boot:run -pl bottin-web 3. Access H2 Console at http://localhost:8080/h2-console -## API Endpoints +## API -### Public Endpoints +The REST API provides: +- **NIP-05 Resolution**: Public `/.well-known/nostr.json` endpoint +- **Records Management**: CRUD operations for NIP-05 identities +- **Domain Management**: Register and verify domains +- **External Verification**: Verify third-party NIP-05 identifiers -| Method | Path | Description | -|--------|------|-------------| -| GET | `/.well-known/nostr.json?name={username}` | NIP-05 lookup | -| GET | `/api/v1/verify?nip05={identifier}` | External NIP-05 verification | - -### REST API (Authenticated) - -**NIP-05 Records:** - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/records` | List records (paginated) | -| GET | `/api/v1/records/{id}` | Get by ID | -| POST | `/api/v1/records` | Create record | -| PUT | `/api/v1/records/{id}` | Update record | -| DELETE | `/api/v1/records/{id}` | Delete record | - -**Domains:** - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/domains` | List domains | -| POST | `/api/v1/domains` | Register domain | -| DELETE | `/api/v1/domains/{id}` | Remove domain | -| POST | `/api/v1/domains/{id}/verify` | Initiate verification | +See the [REST API Reference](docs/reference/rest-api.md) for complete endpoint documentation. ## Configuration @@ -120,17 +100,17 @@ bottin: ### Method 1: DNS TXT Record 1. Register domain via API or admin dashboard -2. Add TXT record to `_bottin.yourdomain.com`: +2. Add TXT record to `_bottin-verification.yourdomain.com`: ``` - bottin-verify= + bottin-verification= ``` -3. Trigger verification check +3. Trigger verification check (DNS propagation may take up to 24 hours) ### Method 2: Well-Known File 1. Register domain via API or admin dashboard 2. Create file at `https://yourdomain.com/.well-known/bottin-verification.txt` -3. Add the verification token as file contents +3. Add the exact verification token as file contents 4. Trigger verification check ## Integration with nsecbunker-java @@ -141,7 +121,7 @@ Add the Spring Boot starter to your project: xyz.tcheeric bottin-spring-boot-starter - 0.1.0-SNAPSHOT + 0.1.0 ``` @@ -203,8 +183,8 @@ mvn package E2E and integration tests are skipped by default and require explicit activation: ```bash -# Run E2E tests -mvn -Pe2e -pl bottin-tests/bottin-e2e test +# Run E2E tests (requires Docker for Testcontainers) +mvn -Pe2e -DskipE2ETests=false -pl bottin-tests/bottin-e2e test # Run integration tests mvn -Pit -pl bottin-tests/bottin-it test @@ -216,15 +196,18 @@ Build Docker images using [Jib](https://github.com/GoogleContainerTools/jib): ```bash # Build to local Docker daemon -mvn -Pdocker jib:dockerBuild -pl bottin-web,bottin-admin-ui +mvn jib:dockerBuild -pl bottin-web,bottin-admin-ui + +# Deploy to Maven repo and push Docker images to registry +mvn deploy -# Push to docker.398ja.xyz registry -mvn -Pdocker jib:build -pl bottin-web,bottin-admin-ui +# Push to registry without deploying Maven artifacts +mvn jib:build -pl bottin-web,bottin-admin-ui ``` -Images are published as: -- `docker.398ja.xyz/bottin-web:latest` -- `docker.398ja.xyz/bottin-admin-ui:latest` +Images are published to `docker.398ja.xyz`: +- `docker.398ja.xyz/bottin-web:0.1.0` / `latest` +- `docker.398ja.xyz/bottin-admin-ui:0.1.0` / `latest` ## License diff --git a/bottin-admin-ui/pom.xml b/bottin-admin-ui/pom.xml index 90e2b3f..74cb1ae 100644 --- a/bottin-admin-ui/pom.xml +++ b/bottin-admin-ui/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-admin-ui @@ -161,6 +161,15 @@ xyz.tcheeric.bottin.admin.app.BottinAdminApplication + + + push-docker-image + deploy + + build + + + diff --git a/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/config/AdminSecurityConfig.java b/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/config/AdminSecurityConfig.java index 2d7646a..4b57c0a 100644 --- a/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/config/AdminSecurityConfig.java +++ b/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/config/AdminSecurityConfig.java @@ -1,6 +1,7 @@ package xyz.tcheeric.bottin.admin.config; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -23,6 +24,12 @@ @RequiredArgsConstructor public class AdminSecurityConfig { + @Value("${bottin.admin.username:admin}") + private String adminUsername; + + @Value("${bottin.admin.password:admin}") + private String adminPassword; + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -57,10 +64,10 @@ public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception @Bean public UserDetailsService adminUserDetailsService(PasswordEncoder passwordEncoder) { - // Default admin user - in production, use database-backed UserDetailsService + // Admin user configured via environment variables UserDetails admin = User.builder() - .username("admin") - .password(passwordEncoder.encode("admin")) + .username(adminUsername) + .password(passwordEncoder.encode(adminPassword)) .roles("ADMIN") .build(); diff --git a/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/controller/RootRedirectController.java b/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/controller/RootRedirectController.java new file mode 100644 index 0000000..2a8fb74 --- /dev/null +++ b/bottin-admin-ui/src/main/java/xyz/tcheeric/bottin/admin/controller/RootRedirectController.java @@ -0,0 +1,16 @@ +package xyz.tcheeric.bottin.admin.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * Controller to redirect root path to admin dashboard. + */ +@Controller +public class RootRedirectController { + + @GetMapping("/") + public String redirectToAdmin() { + return "redirect:/admin"; + } +} diff --git a/bottin-admin-ui/src/main/resources/templates/admin/domain-detail.html b/bottin-admin-ui/src/main/resources/templates/admin/domain-detail.html index 1ad645a..2135496 100644 --- a/bottin-admin-ui/src/main/resources/templates/admin/domain-detail.html +++ b/bottin-admin-ui/src/main/resources/templates/admin/domain-detail.html @@ -93,44 +93,57 @@

Domain Verification

- To verify ownership of this domain, add one of the following: + Current method: DNS_TXT

- -
-

Option 1: DNS TXT Record

+ +
+

DNS TXT Record

Add a TXT record to your DNS:

Name:

- _nostr-verification + _bottin-verification.example.com

Value:

- token-here + bottin-verification=token-here
- -
-

Option 2: Well-Known File

+ +
+

Well-Known File

Create a file at this URL:

- https://example.com/.well-known/nostr-verification + th:text="'https://' + ${domain.name} + '/.well-known/bottin-verification.txt'"> + https://example.com/.well-known/bottin-verification.txt

With content:

token-here
-
- -
+
+
+ +
+ +

or change verification method:

+
+ + +
+

diff --git a/bottin-admin-ui/src/main/resources/templates/admin/domains.html b/bottin-admin-ui/src/main/resources/templates/admin/domains.html index d10c3b2..8269a13 100644 --- a/bottin-admin-ui/src/main/resources/templates/admin/domains.html +++ b/bottin-admin-ui/src/main/resources/templates/admin/domains.html @@ -67,7 +67,7 @@

Domains

0 + th:text="${domain.recordCount}">0 2024-01-01 diff --git a/bottin-core/pom.xml b/bottin-core/pom.xml index 93092fa..2dfa523 100644 --- a/bottin-core/pom.xml +++ b/bottin-core/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-core @@ -18,12 +18,6 @@ Core domain models, interfaces, and exceptions for Bottin NIP-05 registry - - - xyz.tcheeric - nsecbunker-account - - xyz.tcheeric diff --git a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/exception/VerificationFailedException.java b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/exception/VerificationFailedException.java index a92ff39..d2dfd60 100644 --- a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/exception/VerificationFailedException.java +++ b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/exception/VerificationFailedException.java @@ -7,7 +7,7 @@ public class VerificationFailedException extends BottinException { private static final String ERROR_CODE = "VERIFICATION_FAILED"; private static final String DNS_SUGGESTION = - "Ensure the DNS TXT record '_bottin.{domain}' contains the verification token. DNS propagation may take up to 24 hours."; + "Ensure the DNS TXT record '_bottin-verification.{domain}' contains the verification token. DNS propagation may take up to 24 hours."; private static final String WELLKNOWN_SUGGESTION = "Ensure the file at 'https://{domain}/.well-known/bottin-verification.txt' contains the exact verification token."; diff --git a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/Nip05RecordData.java b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/Nip05RecordData.java index 897f49e..634c018 100644 --- a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/Nip05RecordData.java +++ b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/Nip05RecordData.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Builder; import lombok.Value; -import xyz.tcheeric.nsecbunker.account.nip05.Nip05Record; import java.time.Instant; import java.util.Collections; @@ -90,37 +89,6 @@ public List getRelays() { } } - /** - * Converts this to the nsecbunker-java Nip05Record model. - */ - public Nip05Record toNip05Record() { - return Nip05Record.builder() - .nip05(getNip05()) - .pubkey(pubkey) - .relaysJson(relaysJson) - .build(); - } - - /** - * Creates a Nip05RecordData from an nsecbunker-java Nip05Record. - */ - public static Nip05RecordData fromNip05Record(Nip05Record record, Long domainId) { - String username = record.getUsername(); - String domain = record.getDomain(); - Instant now = Instant.now(); - - return Nip05RecordData.builder() - .domainId(domainId) - .username(username) - .domain(domain) - .pubkey(record.getPubkey()) - .relaysJson(record.getRelaysJson()) - .enabled(true) - .createdAt(now) - .updatedAt(now) - .build(); - } - /** * Creates a new NIP-05 record. */ diff --git a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationChallenge.java b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationChallenge.java index 05947b6..af784c5 100644 --- a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationChallenge.java +++ b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationChallenge.java @@ -48,7 +48,7 @@ public class VerificationChallenge { */ public static VerificationChallenge forDnsTxt(String domain, String token, Instant expiresAt) { String instructions = String.format( - "Add a TXT record to _bottin.%s with value: bottin-verify=%s", + "Add a TXT record to _bottin-verification.%s with value: bottin-verification=%s", domain, token); return VerificationChallenge.builder() .domain(domain) @@ -88,6 +88,6 @@ public boolean isExpired() { * Returns the full verification value for DNS TXT records. */ public String getDnsTxtValue() { - return "bottin-verify=" + token; + return "bottin-verification=" + token; } } diff --git a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationMethod.java b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationMethod.java index 0dd9bb8..b367bd0 100644 --- a/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationMethod.java +++ b/bottin-core/src/main/java/xyz/tcheeric/bottin/core/model/VerificationMethod.java @@ -7,9 +7,9 @@ public enum VerificationMethod { /** * DNS TXT record verification. - * User adds a TXT record to _bottin.{domain} with the verification token. + * User adds a TXT record to _bottin-verification.{domain} with the verification token. */ - DNS_TXT("DNS TXT Record", "_bottin.{domain}"), + DNS_TXT("DNS TXT Record", "_bottin-verification.{domain}"), /** * Well-known file verification. diff --git a/bottin-persistence/pom.xml b/bottin-persistence/pom.xml index 9593366..e0cbe84 100644 --- a/bottin-persistence/pom.xml +++ b/bottin-persistence/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-persistence diff --git a/bottin-persistence/src/main/resources/db/migration/V1__initial_schema.sql b/bottin-persistence/src/main/resources/db/migration/V1__initial_schema.sql index b903388..bac47c3 100644 --- a/bottin-persistence/src/main/resources/db/migration/V1__initial_schema.sql +++ b/bottin-persistence/src/main/resources/db/migration/V1__initial_schema.sql @@ -3,7 +3,7 @@ -- domains: Registered domains with verification status CREATE TABLE domains ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, verified BOOLEAN DEFAULT FALSE NOT NULL, verification_token VARCHAR(64), @@ -18,7 +18,7 @@ CREATE INDEX idx_domains_name ON domains(name); -- nip05_records: NIP-05 identifier mappings CREATE TABLE nip05_records ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, domain_id BIGINT NOT NULL, username VARCHAR(255) NOT NULL, pubkey VARCHAR(64) NOT NULL, @@ -35,7 +35,7 @@ CREATE INDEX idx_nip05_records_domain_username ON nip05_records(domain_id, usern -- admin_users: Admin dashboard users CREATE TABLE admin_users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, pubkey VARCHAR(64), @@ -48,7 +48,7 @@ CREATE INDEX idx_admin_users_username ON admin_users(username); -- verification_logs: Audit trail for verifications CREATE TABLE verification_logs ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, nip05 VARCHAR(511) NOT NULL, verification_type VARCHAR(20) NOT NULL, result VARCHAR(20) NOT NULL, diff --git a/bottin-persistence/src/main/resources/db/migration/V2__domain_verification_logs.sql b/bottin-persistence/src/main/resources/db/migration/V2__domain_verification_logs.sql index aacd390..ff921fe 100644 --- a/bottin-persistence/src/main/resources/db/migration/V2__domain_verification_logs.sql +++ b/bottin-persistence/src/main/resources/db/migration/V2__domain_verification_logs.sql @@ -1,6 +1,6 @@ -- Domain verification logs table for tracking verification attempts CREATE TABLE domain_verification_logs ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, domain_id BIGINT NOT NULL, method VARCHAR(20) NOT NULL, success BOOLEAN NOT NULL, diff --git a/bottin-service/pom.xml b/bottin-service/pom.xml index 6ef7bb0..58e370f 100644 --- a/bottin-service/pom.xml +++ b/bottin-service/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-service diff --git a/bottin-spring-boot-starter/pom.xml b/bottin-spring-boot-starter/pom.xml index 92b68ed..0d51e8b 100644 --- a/bottin-spring-boot-starter/pom.xml +++ b/bottin-spring-boot-starter/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-spring-boot-starter diff --git a/bottin-tests/bottin-e2e/pom.xml b/bottin-tests/bottin-e2e/pom.xml index 5f29314..006bfd0 100644 --- a/bottin-tests/bottin-e2e/pom.xml +++ b/bottin-tests/bottin-e2e/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin-tests - 0.1.0-SNAPSHOT + 0.1.0 bottin-e2e diff --git a/bottin-tests/bottin-e2e/src/test/java/xyz/tcheeric/bottin/e2e/TestApplication.java b/bottin-tests/bottin-e2e/src/test/java/xyz/tcheeric/bottin/e2e/TestApplication.java index 616e23a..a7fc695 100644 --- a/bottin-tests/bottin-e2e/src/test/java/xyz/tcheeric/bottin/e2e/TestApplication.java +++ b/bottin-tests/bottin-e2e/src/test/java/xyz/tcheeric/bottin/e2e/TestApplication.java @@ -5,7 +5,6 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; diff --git a/bottin-tests/bottin-e2e/src/test/resources/application-e2e.yml b/bottin-tests/bottin-e2e/src/test/resources/application-e2e.yml index 8f3881b..c071646 100644 --- a/bottin-tests/bottin-e2e/src/test/resources/application-e2e.yml +++ b/bottin-tests/bottin-e2e/src/test/resources/application-e2e.yml @@ -11,6 +11,7 @@ spring: hibernate: ddl-auto: create-drop show-sql: false + database-platform: org.hibernate.dialect.PostgreSQLDialect properties: hibernate: # Suppress PostgreSQL createClob() warning diff --git a/bottin-tests/bottin-it/pom.xml b/bottin-tests/bottin-it/pom.xml index 1168330..acf08c8 100644 --- a/bottin-tests/bottin-it/pom.xml +++ b/bottin-tests/bottin-it/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin-tests - 0.1.0-SNAPSHOT + 0.1.0 bottin-it diff --git a/bottin-tests/pom.xml b/bottin-tests/pom.xml index e172e38..3ce1c5a 100644 --- a/bottin-tests/pom.xml +++ b/bottin-tests/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-tests diff --git a/bottin-verification/pom.xml b/bottin-verification/pom.xml index f648ebc..c0e6d56 100644 --- a/bottin-verification/pom.xml +++ b/bottin-verification/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-verification diff --git a/bottin-verification/src/main/java/xyz/tcheeric/bottin/verification/ExternalNip05Verifier.java b/bottin-verification/src/main/java/xyz/tcheeric/bottin/verification/ExternalNip05Verifier.java index dec2eaa..15f4d66 100644 --- a/bottin-verification/src/main/java/xyz/tcheeric/bottin/verification/ExternalNip05Verifier.java +++ b/bottin-verification/src/main/java/xyz/tcheeric/bottin/verification/ExternalNip05Verifier.java @@ -90,11 +90,11 @@ public ExternalNip05VerificationResult verify(String nip05) { return parseAndValidateResponse(nip05, parsed, response); } catch (WebClientResponseException e) { String errorMessage = "HTTP error " + e.getStatusCode().value() + " fetching nostr.json"; - log.debug("external_nip05_verification_http_error nip05={} status={}", nip05, e.getStatusCode()); + log.debug("external_nip05_cached_http_error nip05={} status={}", nip05, e.getStatusCode()); return logAndReturnFailure(nip05, errorMessage, null); } catch (Exception e) { String errorMessage = extractErrorMessage(e); - log.error("external_nip05_verification_error nip05={} error={}", nip05, errorMessage, e); + log.error("external_nip05_cached_error nip05={} error={}", nip05, errorMessage, e); return logAndReturnFailure(nip05, "Failed to verify: " + errorMessage, null); } } @@ -106,7 +106,7 @@ public ExternalNip05VerificationResult verify(String nip05) { * @return the verification result */ public ExternalNip05VerificationResult verifyNoCache(String nip05) { - log.debug("external_nip05_verification_nocache_start nip05={}", nip05); + log.debug("external_nip05_nocache_start nip05={}", nip05); if (nip05 == null || nip05.isBlank()) { return logAndReturnFailure(nip05, "NIP-05 identifier is required", null); @@ -124,11 +124,11 @@ public ExternalNip05VerificationResult verifyNoCache(String nip05) { return parseAndValidateResponse(nip05, parsed, response); } catch (WebClientResponseException e) { String errorMessage = "HTTP error " + e.getStatusCode().value() + " fetching nostr.json"; - log.debug("external_nip05_verification_http_error nip05={} status={}", nip05, e.getStatusCode()); + log.debug("external_nip05_nocache_http_error nip05={} status={}", nip05, e.getStatusCode()); return logAndReturnFailure(nip05, errorMessage, null); } catch (Exception e) { String errorMessage = extractErrorMessage(e); - log.error("external_nip05_verification_error nip05={} error={}", nip05, errorMessage, e); + log.error("external_nip05_nocache_error nip05={} error={}", nip05, errorMessage, e); return logAndReturnFailure(nip05, "Failed to verify: " + errorMessage, null); } } diff --git a/bottin-web/pom.xml b/bottin-web/pom.xml index c38a87e..7d0a5db 100644 --- a/bottin-web/pom.xml +++ b/bottin-web/pom.xml @@ -8,7 +8,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 bottin-web @@ -167,6 +167,15 @@ xyz.tcheeric.bottin.web.app.BottinWebApplication + + + push-docker-image + deploy + + build + + + diff --git a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/config/SecurityConfig.java b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/config/SecurityConfig.java index 62c80a9..2decdde 100644 --- a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/config/SecurityConfig.java +++ b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/config/SecurityConfig.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -29,6 +30,7 @@ */ @Configuration @EnableWebSecurity +@Profile("!e2e") public class SecurityConfig { @Value("${bottin.admin.username:admin}") diff --git a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/DomainController.java b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/DomainController.java index 071d90a..e1d3ea2 100644 --- a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/DomainController.java +++ b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/DomainController.java @@ -199,11 +199,11 @@ public ResponseEntity initiateVerification( @Parameter(description = "Domain ID") @PathVariable Long id, @Valid @RequestBody InitiateVerificationRequest request) { - log.info("verification_initiate domain_id={} method={}", id, request.getMethod()); + log.info("verification_initiate_via_body domain_id={} method={}", id, request.getMethod()); VerificationChallenge challenge = verificationService.initiateVerification(id, request.getMethod()); - log.info("verification_challenge_created domain_id={} method={} already_verified={}", + log.info("verification_challenge_created_via_body domain_id={} method={} already_verified={}", id, request.getMethod(), challenge.isAlreadyVerified()); return ResponseEntity.ok(VerificationChallengeResponse.from(challenge)); @@ -260,19 +260,19 @@ public ResponseEntity initiateVerificationWithMet @Parameter(description = "Verification method (DNS_TXT or WELL_KNOWN_FILE)") @PathVariable("method") String methodStr) { - log.info("verification_initiate domain_id={} method={}", id, methodStr); + log.info("verification_initiate_via_path domain_id={} method={}", id, methodStr); VerificationMethod method; try { method = VerificationMethod.valueOf(methodStr.toUpperCase()); } catch (IllegalArgumentException e) { - log.warn("verification_invalid_method domain_id={} method={}", id, methodStr); + log.warn("verification_invalid_method_in_path domain_id={} method={}", id, methodStr); return ResponseEntity.badRequest().build(); } VerificationChallenge challenge = verificationService.initiateVerification(id, method); - log.info("verification_challenge_created domain_id={} method={} already_verified={}", + log.info("verification_challenge_created_via_path domain_id={} method={} already_verified={}", id, method, challenge.isAlreadyVerified()); return ResponseEntity.ok(VerificationChallengeResponse.from(challenge)); diff --git a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/GlobalExceptionHandler.java b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/GlobalExceptionHandler.java index adc8e0c..1f2289f 100644 --- a/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/GlobalExceptionHandler.java +++ b/bottin-web/src/main/java/xyz/tcheeric/bottin/web/controller/GlobalExceptionHandler.java @@ -100,11 +100,16 @@ public ResponseEntity handleMethodNotSupported( log.warn("method_not_allowed path={} method={}", request.getRequestURI(), ex.getMethod()); + String[] supportedMethods = ex.getSupportedMethods(); + String suggestion = supportedMethods != null && supportedMethods.length > 0 + ? "Use one of the supported methods: " + String.join(", ", supportedMethods) + : "Check the API documentation for supported methods"; + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED) .body(ErrorResponse.withSuggestion( "METHOD_NOT_ALLOWED", "HTTP method " + ex.getMethod() + " is not supported for this endpoint", - "Use one of the supported methods: " + String.join(", ", ex.getSupportedMethods()), + suggestion, request.getRequestURI() )); } diff --git a/docker-compose.yml b/docker-compose.yml index 78396fa..474f55f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ -version: '3.8' - services: bottin-web: - build: - context: . - dockerfile: Dockerfile.web + image: docker.398ja.xyz/bottin-web:latest + # To build locally instead, uncomment: + # build: + # context: . + # dockerfile: Dockerfile.web container_name: bottin-web ports: - "${BOTTIN_PORT:-8080}:8080" @@ -32,9 +32,11 @@ services: - bottin-network bottin-admin: - build: - context: . - dockerfile: Dockerfile.admin + image: docker.398ja.xyz/bottin-admin-ui:latest + # To build locally instead, uncomment: + # build: + # context: . + # dockerfile: Dockerfile.admin container_name: bottin-admin ports: - "${BOTTIN_ADMIN_PORT:-8081}:8081" diff --git a/docs/README.md b/docs/README.md index 3822dc8..f7fed76 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ Learning-oriented guides that take you through a series of steps to complete a p Problem-oriented guides that show you how to achieve a specific goal. - [Deploy with Docker](how-to/docker-deployment.md) - Deploy Bottin services using Docker Compose +- [Running E2E Tests](how-to/running-e2e-tests.md) - Run end-to-end tests with Testcontainers ## Reference diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md index ad336a6..f4174e3 100644 --- a/docs/explanation/architecture.md +++ b/docs/explanation/architecture.md @@ -15,7 +15,9 @@ bottin/ ├── bottin-web/ # REST API controllers ├── bottin-admin-ui/ # Admin dashboard (Thymeleaf) ├── bottin-spring-boot-starter/ # Auto-configuration -└── bottin-tests/ # Integration tests +└── bottin-tests/ # Test modules + ├── bottin-it/ # Integration tests + └── bottin-e2e/ # End-to-end tests with Testcontainers ``` ### Module Dependencies @@ -125,3 +127,47 @@ Both services share common configuration through: - Environment variables for deployment customization See [Docker Compose Configuration](../reference/docker-compose-configuration.md) for deployment options. + +## Testing Architecture + +Bottin uses a layered testing approach with dedicated test modules. + +### Test Module Structure + +``` +bottin-tests/ +├── bottin-it/ # Integration tests (mint-specific) +└── bottin-e2e/ # End-to-end tests + ├── BasicE2ETest # Tests without external containers + ├── BaseE2ETest # Tests with full container infrastructure + └── TestContainersConfig # Testcontainers configuration +``` + +### E2E Test Infrastructure + +The E2E tests use [Testcontainers](https://testcontainers.org/) to spin up real infrastructure: + +- **PostgreSQL** - Database for persistent storage +- **nsecbunkerd** - Key management container for integration testing +- **strfry** - Nostr relay (optional, for NIP-46 tests) + +### Test Profiles + +Maven profiles control test execution: + +| Profile | Command | Description | +|---------|---------|-------------| +| default | `mvn test` | Unit tests only | +| e2e | `mvn -Pe2e test` | E2E tests with Testcontainers | +| it | `mvn -Pit test` | Integration tests | + +### nsecbunker-java Integration Tests + +The `NsecbunkerIntegrationE2ETest` verifies the integration between bottin's `PersistentNip05Manager` and nsecbunker-java: + +- NIP-05 record creation with generated keypairs +- NIP-05 verification and lookup +- Record management (CRUD operations) +- Well-known endpoint integration + +See [Running E2E Tests](../how-to/running-e2e-tests.md) for detailed instructions. diff --git a/docs/how-to/docker-deployment.md b/docs/how-to/docker-deployment.md index 0ab099e..6ac0b75 100644 --- a/docs/how-to/docker-deployment.md +++ b/docs/how-to/docker-deployment.md @@ -97,37 +97,58 @@ docker-compose down -v Bottin uses [Google Jib](https://github.com/GoogleContainerTools/jib) for building optimized Docker images without requiring a Docker daemon. +### Deploy with Maven (Recommended) + +The `mvn deploy` goal automatically pushes Docker images to the registry along with Maven artifacts: + +```bash +# Deploy everything (JARs to Maven repo + Docker images to registry) +mvn deploy + +# Deploy specific modules only +mvn deploy -pl bottin-web,bottin-admin-ui -am +``` + +This requires registry credentials in `~/.m2/settings.xml`: + +```xml + + docker.398ja.xyz + your-username + your-password + +``` + ### Build to Local Docker Daemon +For local development, build images to your local Docker daemon: + ```bash # Build both services to local Docker -mvn -Pdocker jib:dockerBuild -pl bottin-web,bottin-admin-ui +mvn jib:dockerBuild -pl bottin-web,bottin-admin-ui # Build a single service -mvn -Pdocker jib:dockerBuild -pl bottin-web +mvn jib:dockerBuild -pl bottin-web ``` -### Push to Private Registry +### Push to Private Registry (Manual) -Push images to `docker.398ja.xyz`: +Push images manually without deploying Maven artifacts: ```bash -# Login to registry first -docker login docker.398ja.xyz - # Build and push both services -mvn -Pdocker jib:build -pl bottin-web,bottin-admin-ui +mvn jib:build -pl bottin-web,bottin-admin-ui # Build and push a single service -mvn -Pdocker jib:build -pl bottin-web +mvn jib:build -pl bottin-web ``` ### Image Tags Images are tagged with both the version and `latest`: -- `docker.398ja.xyz/bottin-web:0.1.0-SNAPSHOT` +- `docker.398ja.xyz/bottin-web:0.1.0` - `docker.398ja.xyz/bottin-web:latest` -- `docker.398ja.xyz/bottin-admin-ui:0.1.0-SNAPSHOT` +- `docker.398ja.xyz/bottin-admin-ui:0.1.0` - `docker.398ja.xyz/bottin-admin-ui:latest` ### Container Configuration diff --git a/docs/how-to/running-e2e-tests.md b/docs/how-to/running-e2e-tests.md new file mode 100644 index 0000000..fa511a1 --- /dev/null +++ b/docs/how-to/running-e2e-tests.md @@ -0,0 +1,170 @@ +# Running E2E Tests + +This guide shows you how to run the end-to-end tests for Bottin. These tests verify the full system behavior using real containers managed by Testcontainers. + +## Prerequisites + +- Java 21 +- Maven 3.8+ +- Docker Engine 20.10+ (running) + +## Run All E2E Tests + +Execute all E2E tests with the `e2e` Maven profile: + +```bash +mvn -Pe2e -pl bottin-tests/bottin-e2e test +``` + +This starts: +- PostgreSQL container for database +- nsecbunkerd container for key management integration +- The Spring Boot test application + +## Run Specific Test Classes + +Run a single test class: + +```bash +# Security tests +mvn -Pe2e -pl bottin-tests/bottin-e2e test -Dtest=SecurityE2ETest + +# REST API CRUD tests +mvn -Pe2e -pl bottin-tests/bottin-e2e test -Dtest=RestApiCrudE2ETest + +# NIP-05 registration flow +mvn -Pe2e -pl bottin-tests/bottin-e2e test -Dtest=Nip05RegistrationFlowE2ETest + +# Error handling tests +mvn -Pe2e -pl bottin-tests/bottin-e2e test -Dtest=ErrorHandlingE2ETest + +# nsecbunker-java integration +mvn -Pe2e -pl bottin-tests/bottin-e2e test -Dtest=NsecbunkerIntegrationE2ETest +``` + +## Run a Single Test Method + +```bash +mvn -Pe2e -pl bottin-tests/bottin-e2e test \ + -Dtest=SecurityE2ETest#shouldAllowAccessWithValidCredentials +``` + +## Test Categories + +### Basic E2E Tests + +Tests that only require PostgreSQL: + +| Test Class | Description | +|------------|-------------| +| `SecurityE2ETest` | Authentication and authorization | +| `RestApiCrudE2ETest` | REST API CRUD operations | +| `Nip05RegistrationFlowE2ETest` | NIP-05 registration workflow | +| `ErrorHandlingE2ETest` | Error responses and validation | + +### Integration E2E Tests + +Tests that require additional containers: + +| Test Class | Containers | Description | +|------------|------------|-------------| +| `NsecbunkerIntegrationE2ETest` | PostgreSQL, nsecbunkerd | PersistentNip05Manager integration | + +## Container Images + +The tests use these container images: + +| Container | Image | Purpose | +|-----------|-------|---------| +| PostgreSQL | `postgres:16-alpine` | Database | +| nsecbunkerd | `docker.398ja.xyz/nsecbunkerd:latest` | Key management | +| strfry | `dockurr/strfry:latest` | Nostr relay (optional) | + +Pull images in advance to speed up test execution: + +```bash +docker pull postgres:16-alpine +docker pull docker.398ja.xyz/nsecbunkerd:latest +docker pull dockurr/strfry:latest +``` + +## Test Configuration + +E2E tests use the `e2e` Spring profile with these settings: + +| Property | Value | Description | +|----------|-------|-------------| +| Database | PostgreSQL (Testcontainers) | Real database via container | +| Schema | `create-drop` | Fresh schema per test | +| Admin user | `admin` | Test admin username | +| Admin password | `e2e-test-password` | Test admin password | + +Configuration file: `bottin-tests/bottin-e2e/src/test/resources/application-e2e.yml` + +## Troubleshooting + +### Docker Not Running + +``` +Could not find a valid Docker environment +``` + +Start Docker daemon: +```bash +# Linux +sudo systemctl start docker + +# macOS +open -a Docker +``` + +### Container Startup Timeout + +If containers fail to start within the timeout: + +1. Pull images manually first +2. Check Docker resource limits +3. Increase timeout in `TestContainersConfig.java` + +### Authentication Failures (401) + +If tests fail with 401 errors: + +1. Verify `TestSecurityConfig` is loaded +2. Check credentials match `application-e2e.yml` +3. Run with debug logging: + ```bash + mvn -Pe2e -pl bottin-tests/bottin-e2e test \ + -Dlogging.level.org.springframework.security=DEBUG + ``` + +### Port Conflicts + +Testcontainers uses random ports. If you see port binding errors: + +1. Check for stale containers: `docker ps -a` +2. Clean up: `docker container prune` + +## Verify Full Build + +Run the complete verification including E2E tests: + +```bash +mvn verify +``` + +This runs: +1. Unit tests (all modules) +2. Integration tests (if configured) +3. E2E tests are skipped by default in `verify` + +To include E2E tests in verify: + +```bash +mvn -Pe2e verify +``` + +## Next Steps + +- See [Architecture Overview](../explanation/architecture.md) for test module structure +- See [REST API Reference](../reference/rest-api.md) for API endpoints being tested diff --git a/pom.xml b/pom.xml index 0e66636..a48579f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ xyz.tcheeric bottin - 0.1.0-SNAPSHOT + 0.1.0 pom Bottin - NIP-05 Registry Service @@ -53,10 +53,10 @@ 21 - 3.2.4 + 3.4.1 - 0.1.0-SNAPSHOT + 0.1.0 1.2.0 @@ -88,7 +88,7 @@ 1.9.10 - 3.5.3 + 3.6.0 3.13.0