Updates documentation.

This commit is contained in:
2026-10-22 17:33:45 -07:00
parent 37f5e19fbd
commit 5de22ec0ea
12 changed files with 348 additions and 72 deletions

View File

@@ -18,7 +18,7 @@ MODRINTH_TOKEN=
MODRINTH_PROJECT_ID=metropolis-builder MODRINTH_PROJECT_ID=metropolis-builder
# Optional Markdown override; empty generates notes from commits since the last tag. # Optional Markdown override; empty generates notes from commits since the last tag.
MODRINTH_CHANGELOG= MODRINTH_CHANGELOG=
MODRINTH_GAME_VERSIONS=26.2 MODRINTH_GAME_VERSIONS=26.2,26.3
MODRINTH_LOADERS=paper MODRINTH_LOADERS=paper
MODRINTH_VERSION_TYPE= MODRINTH_VERSION_TYPE=
MODRINTH_DEPENDENCIES= MODRINTH_DEPENDENCIES=

1
.gitignore vendored
View File

@@ -6,4 +6,5 @@ bin/
/backups/ /backups/
/server/ /server/
.env .env
.settings/

35
AGENTS.md Normal file
View File

@@ -0,0 +1,35 @@
# Metropolis Working Rules
Start with [CONTRIBUTING.md](CONTRIBUTING.md) for the first-contribution path,
architecture, build/test commands, and operational safety. [README.md](README.md)
is the player/server-admin behavior reference. Other files in `docs/` are
optional reference material, not competing onboarding guides.
- Use **Metropolis** in human-facing prose. Preserve machine-facing identifiers,
including `Metropolis`, `com.majinnaibu.bukkitplugins.metropolis`, artifact
names, plugin IDs, commands, permission nodes, and URLs.
- Inspect the current diff before editing and preserve unrelated work. Do not
edit generated `build/`, `target/`, or `.gradle/` output. Never read, expose,
or commit `.env`, server/player data, or publishing credentials.
- Keep claims aligned with source, build configuration, CI, and scripts. State
Paper targets separately from versions actually built or tested. Keep
implemented behavior, planned work, automated coverage, and manual scenarios
distinct.
- When editing Java, preserve the existing AGPL header, use Gradle Spotless,
and keep Bukkit/Paper state and API access on the main thread. Avoid unrelated
gameplay changes.
- Before editing `docs/project-description.json`, read
`docs/projects.schema.json`. Validate the JSON against that schema without
changing the schema to accommodate invalid metadata. Preserve exactly the
existing top-level project key, `id`, `title`, `docId`, `image.url`, and
`headerImage.url` when present. Preserve `headerImage: null` or its absence.
These are fixed consuming-project integration values: do not derive or
“correct” their spelling, capitalization, spacing, or slug from repository
names. Image paths are relative to the consuming project's root; do not
resolve them here, convert them to repository URLs, or replace them with
generated/remote URLs. Preserve all other integration fields unless the task
requires a change. If schema compliance conflicts with preserving protected
values, stop and explain the conflict before editing.
- Do not start/stop servers, deploy, restore, publish, tag, or release without
explicit authorization. `tools/stop-server.sh` can match unrelated Paper
processes; `tools/restore-server.sh` replaces archive top-level items.

View File

@@ -1,18 +1,57 @@
# Contributing # Contributing to Metropolis
## Development Setup This is the single onboarding entry point for new contributors. Shared rules
for people and coding agents are in [AGENTS.md](AGENTS.md); read them before
editing, especially the scraper-metadata preservation and operational-safety
rules.
Use Java 25 or newer. The Gradle wrapper downloads Gradle 9.8; dependencies are declared in `build.gradle` and resolved from Maven Central, PaperMC, and EngineHub. ## First Contribution
```sh 1. Read [README.md](README.md) for supported server versions, installation,
./gradlew build configuration, and commands.
``` 2. Trace the relevant implementation using the source map below and check
nearby tests before changing behavior.
3. Make a focused change. Add or update automated tests for behavior changes;
update the manual pre-release checklist for behavior that needs a real
server.
4. Run `./gradlew test` and `./gradlew build`. Report any manual scenarios that
were not run rather than describing them as passing.
5. Update user-facing descriptions if setup, supported versions, commands,
dependencies, or behavior changed.
The standard build runs JUnit 5 tests without starting Minecraft. Tests use Mockito for Bukkit and external-plugin boundaries. Keep domain and configuration behavior testable without a running server. ## Architecture and Source Map
## Live Server Tests - `MetropolisPlugin` loads configuration, connects Bukkit to WorldGuard and
WorldEdit, allocates plots, generates blocks, and manages regions.
- `Plot`, `PlayerHome`, grid/layout/support classes, and home stores implement
plot geometry, region ownership, allocation, and selected-home persistence.
- `commands/` contains the command handlers; `eventlisteners/` contains join
behavior.
- `src/main/resources/plugin.yml` declares dependencies, commands, and command
permissions. `config.yml` is the bundled configuration template.
- `src/test/` contains isolated JUnit/Mockito tests. `src/integrationTest/`
contains the opt-in tests that require a running server and RCON.
- `build.gradle` defines dependencies, Java/Gradle versions, formatting, and
test tasks. `.github/workflows/gradle.yml` runs tests and builds on pushes
and pull requests to `develop`.
Live tests are opt-in and run separately from `build`: ## Prerequisites and Checks
- JDK 25. The Gradle wrapper uses Gradle 9.8 and can download the required
Gradle distribution; dependencies resolve from Maven Central, PaperMC, and
EngineHub.
- `./gradlew test` runs server-free JUnit tests.
- `./gradlew build` runs the normal build and verification lifecycle. The
plugin jar is written under `build/libs/`.
- CI compiles against Paper API 26.2, runs `test`, then builds. It does not
start a Minecraft server or run `integrationTest`.
The opt-in `integrationTest` source set currently has two tests: checking that
Metropolis appears in `/plugins`, and creating a coordinate reservation then
checking WorldGuard's `regions.yml`. It mutates the configured server's
WorldGuard data and needs a running Paper server with RCON, WorldGuard, and
WorldEdit. Use a disposable server, bind RCON to a trusted interface, and use
a temporary password:
```sh ```sh
METROPOLIS_RCON_HOST=127.0.0.1 \ METROPOLIS_RCON_HOST=127.0.0.1 \
@@ -22,12 +61,27 @@ METROPOLIS_SERVER_DIR=/path/to/server \
./gradlew integrationTest ./gradlew integrationTest
``` ```
Use a disposable or backed-up Paper server with compatible WorldGuard and WorldEdit plugin jars installed. Keep RCON local, use a temporary password, and restore the server after tests that modify regions or worlds. Set `METROPOLIS_WORLD` if the test world is not named `world`. These tests do
not cover the broader in-game workflows in [human-tests.md](docs/human-tests.md).
## Pull Requests ## Operational Safety
- Keep changes focused and include tests for behavior changes. Use the build/test commands above for ordinary contribution work. The scripts
- Run `./gradlew build` before submitting. under `tools/` also include server start/stop, deployment, backup/restore, and
- Run `integrationTest` only when changing behavior that requires Paper or the external plugins; do not add it to the default build. publishing operations; they are not required for a normal build. Read a
- Update `README.md` and project descriptions when user-facing setup, commands, or dependencies change. script before use and get explicit authorization before running operational
- Keep generated build output, server data, credentials, and local `.env` files out of commits. or release actions. In particular, `stop-server.sh` matches Paper jar
processes by command line, and `restore-server.sh` replaces matching top-level
server items from an archive. Never commit `.env`, credentials, server state,
or generated build output.
## Optional References
- [README.md](README.md): player/server-admin usage reference.
- [human-tests.md](docs/human-tests.md): manual pre-release checklist, not an
automated test report.
- [TODO.md](docs/TODO.md): historical backlog; the maintained plan is
[modernization-plan.md](docs/modernization-plan.md).
- [Modrinth description](docs/modrinth-description.md): hosting copy reference.
- [Scraper metadata](docs/project-description.md) and
[schema](docs/projects.schema.json): consuming-site project entry.

View File

@@ -18,7 +18,9 @@ Metropolis expands a protected Minecraft city as players join. Each player can r
## Requirements ## Requirements
- Paper 26.2 or 26.3, with Java 25 or newer - Paper 26.2 or 26.3, with Java 25 or newer. The build and CI compile against
Paper API 26.2; CI does not launch a server to verify runtime behavior on
either version.
- WorldGuard 7.0.19 - WorldGuard 7.0.19
- WorldEdit 7.4.5 - WorldEdit 7.4.5
@@ -114,4 +116,9 @@ Assign these permission nodes to groups or individual users in your permission p
## License ## License
GNU General Public License v3. GNU Affero General Public License v3 or later.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for the single developer onboarding
path, source map, checks, and operational safety notes.

View File

@@ -1,4 +1,9 @@
# Modernize Metropolis Plugin: Implementation Tasks # Historical Modernization Backlog
This file preserves the earlier task list for context. Its status labels and
acceptance text are historical and are not the current implementation report.
Use the maintained [modernization plan](modernization-plan.md) for current
implementation status, pending work, and completion criteria.
| ID | Status | Title | | ID | Status | Title |
|:-------:|:--------:|:------| |:-------:|:--------:|:------|

View File

@@ -1,6 +1,10 @@
# Metropolis Human Testing # Metropolis Manual Pre-release Checklist
Use this checklist for behavior that depends on a real Paper world, player interactions, or the installed WorldGuard and WorldEdit plugins. Automated tests cover some isolated logic, but they do not establish that the complete in-game workflows behave correctly. This is a manual checklist of expected behavior that needs a real Paper world,
player interaction, or installed WorldGuard/WorldEdit plugins. Checklist items
are scenarios to run, not test results; no item is considered passed until a
person performs it and records the outcome. Automated coverage is summarized
separately below.
## Test Environment ## Test Environment
@@ -10,7 +14,7 @@ Use this checklist for behavior that depends on a real Paper world, player inter
- Keep a server console open and check it for errors after startup, commands, joins, and restarts. - Keep a server console open and check it for errors after startup, commands, joins, and restarts.
- Record the Metropolis config, world name, test player UUIDs, command output, and any unexpected region bounds or block changes. - Record the Metropolis config, world name, test player UUIDs, command output, and any unexpected region bounds or block changes.
## Human Checklist ## Manual Scenarios
### Startup and world setup ### Startup and world setup
@@ -78,34 +82,20 @@ Use this checklist for behavior that depends on a real Paper world, player inter
- Test commands as console, permitted player, and player without the relevant permission. Check player-only commands, target-player arguments, unknown names, malformed arguments, and permission denials for useful feedback and no unintended changes. - Test commands as console, permitted player, and player without the relevant permission. Check player-only commands, target-player arguments, unknown names, malformed arguments, and permission denials for useful feedback and no unintended changes.
- If upgrading an existing server, verify a legacy name-keyed `currentHomes.yml` entry resolves to the right UUID and the player's selected home is retained. - If upgrading an existing server, verify a legacy name-keyed `currentHomes.yml` entry resolves to the right UUID and the player's selected home is retained.
## Existing Automated Coverage ## Automated Coverage (Separate from This Checklist)
Run `./gradlew test` for the server-free JUnit suite. It has 62 tests covering: Run `./gradlew test` for the server-free JUnit suite. It covers isolated plot
geometry/allocation rules, road and stair layouts/build plans, WorldGuard
region occupancy and reservation lookup, spawn/city bounds, home identity and
selection persistence, permission overrides, player lookup, and selected
command/event behavior. These unit tests do not load a real Paper server.
- `AvenueStairwayLayoutTest`: centered per-segment street and avenue flights, both border directions, independent cadence, top-tread alignment, inverted backing plan, exact-width openings, and invalid-fit rejection. The separate `src/integrationTest` source set currently contains two live
- `PlotLevelSupportTest`: rejection of upper plots with missing logical support cells and complete coverage requirements across all lower levels. checks: Metropolis appears in `/plugins`, and a coordinate reservation is
- `CityRegionBoundsTest`: City bounds expand to include elevated plots and Spawn without shrinking existing protection bounds. persisted in WorldGuard's `regions.yml`. It does not verify restart behavior,
- `PlotGridLayoutTest`: zero-offset compatibility, offsets on X/Y/Z, axis-specific spacing, and negative logical-index recovery. join allocation, WorldEdit-selection reservations, permissions, teleport
- `WorldGuardRegionOccupancyTest`: arbitrary saved-region bounds are excluded while City/global regions are treated as non-occupying envelopes. workflows, generated blocks, or protection behavior. See
- `PlotWorldBorderBoundsTest`: search bounds follow WorldBorder size/center and require the full candidate footprint inside it. [CONTRIBUTING.md](../CONTRIBUTING.md) for prerequisites and safe invocation.
- `SpawnLayoutTest`: default one-cell bounds, independent multipliers, and build-height validation. Those integration checks have not been run as part of this documentation
- `AvenueStairwayBuilderTest`: configured stair material and facing/half data for avenue and street directions, upside-down backing stairs, exact upper-road AIR placements, and no block writes at lower-road Y. update. The manual scenarios above remain unverified until individually run
- `RoadLayoutTest`: independently sized street/avenue bands, correct road-type tagging, non-overlapping corners, and disabled roads at zero width. and recorded against the target server/plugins.
- `RoadBlockWriterTest`: no-op writes for existing roads, filling missing road blocks, and preserving stair treads.
- `CuboidTest`: WorldEdit bound conversion, vector ordering/null handling, touching intersections, and point containment.
- `CurrentHomesStoreTest`: UUID-based persistence and resolving a legacy player-name key.
- `PlayerJoinListenerTest`: the welcome bounds message when a home is supplied by a mock.
- `PlayerLookupTest`: case-insensitive full-name matching and rejection of prefix matching.
- `PlayerHomeTest`: UUID-based region identity and display-name retention.
- `HomePermissionOverrideTest`: dotted permission-node parsing, priority and tie resolution, invalid entries, and username/permission/default precedence.
- `HomeNumberAllocatorTest`: choosing the first unused positive home number.
- `MetropolisHomeMoveCommandTest`: selecting a sender's home, selecting an offline target's home, and rejecting invalid requests.
- `PlotReservationLookupTest`: owner/member matching, exclusion of homes/City/Spawn, legacy reservation markers, and region-name aliases.
- `MetropolisPlotGoCommandTest`: self and target teleportation, ambiguous memberships, absent membership, and unsafe-destination feedback.
- `MetropolisPlotReserveCommandTest`: parsing six coordinates and rejecting malformed coordinates.
- `MetropolisHomeAcquireTest`: acquisition below the limit and refusal at the limit.
- `MetropolisDebugGenerateTestHomesCommandTest`: asynchronous tick-queued generation, one home per interval, no overlapping batches, and continuing test-player names across repeated runs.
The two opt-in tests in `src/integrationTest` require a running Paper server and RCON. They check that Metropolis appears in `/plugins` and that a coordinate reservation is persisted by WorldGuard. They do not currently verify restart persistence, join allocation, WorldEdit-selection reservations, permissions, teleports, generated blocks, protection behavior, or the other command workflows above. Run them with the environment variables documented in `README.md`; use a disposable server because the reservation test changes its WorldGuard data.
The integration scope described in `TODO.md` is broader than the tests currently present. Treat the scenarios in this guide as unverified until a human has run them on the target Paper and plugin versions.

View File

@@ -0,0 +1,59 @@
# Metropolis Modernization Plan
This is the maintained status and follow-up plan. Implemented behavior is
described in [README.md](../README.md); automated and manual coverage are
separated in [human-tests.md](human-tests.md). A feature is not considered
runtime-verified merely because its code or an isolated unit test exists.
## Completed
- **Build and CI modernization.** Gradle 9.8 builds the Java 25 plugin and
expands the version into `plugin.yml`. CI runs `test` and the plugin build
for pushes and pull requests to `develop`. Completion check: running
`./gradlew test build` succeeds; CI provides the corresponding hosted check.
- **Paper/WorldGuard/WorldEdit integration.** The plugin declares Paper API
26.2, WorldGuard 7.0.19, and WorldEdit 7.4.5, and uses WorldGuard regions
for city, home, and reservation geometry. Paper 26.2/26.3 are project
targets; the current build and CI do not perform live server compatibility
checks. Completion check for runtime claims: run the applicable manual
scenarios in `human-tests.md` on a disposable server.
- **Plot, home, and command features.** The implementation includes join-time
initial home allocation, explicit additional-home acquisition, UUID-based
active-home persistence with legacy name-key loading, user/permission plot
overrides, reservations, multi-level support checks, and configured
road/stair generation. Isolated tests cover parts of these rules; see
`src/test/` and `human-tests.md` for their boundaries.
## Pending
- **Expand live-server integration coverage.** The current `integrationTest`
suite only checks plugin visibility and persistence of one coordinate-based
WorldGuard reservation. Add focused disposable-server checks for join-time
home allocation/ownership and selected-home restart persistence. Completion
criteria: tests assert the expected region/UUID state before and after a
restart, clean up their own test regions, and remain excluded from `build`.
- **Choose optional login allocation policies (MET-023/MET-024).** Current
behavior allocates the configured initial home count (default one) when a
player has no home; later homes require `/metropolis-home-acquire`. Decide
whether to offer grant-all-to-limit or one-more-per-login policies before
implementing them. Completion criteria: documented config semantics,
compatibility defaults, and unit tests for limits, reconnects, and existing
home selections.
- **Choose an allocation traversal option (MET-025).** The proposal to select
a rectangle/cuboid or ring-based traversal remains unimplemented. First
document deterministic ordering and how each policy treats occupied regions,
levels, and WorldBorder bounds. Completion criteria: configuration and
focused allocation tests establish repeatable ordering without overlaps.
- **Research conversion of existing worlds (MET-026).** There is no automatic
world/config conversion workflow. Document the legacy data/config formats,
backups and rollback requirements, and a safe migration strategy before
proposing implementation. Completion criteria: a reviewed conversion
specification plus disposable-world validation; do not claim migration
support before that work is complete.
## Deferred Ideas
Economy-based home sales, arbitrary connected plot shapes, and alternate
multi-plot footprints remain product ideas, not committed work. Reconsider them
only after requirements, compatibility constraints, and acceptance criteria
are defined.

View File

@@ -11,6 +11,8 @@ Metropolis grows a protected Minecraft city as players join. It assigns home plo
WorldGuard and WorldEdit are required dependencies. Install all three plugins in the server's `plugins` directory. WorldGuard and WorldEdit are required dependencies. Install all three plugins in the server's `plugins` directory.
Metropolis targets Paper only; Bukkit and Spigot servers are not supported. Metropolis targets Paper only; Bukkit and Spigot servers are not supported.
The project targets Paper 26.2 and 26.3, while its build/CI compile against the
Paper 26.2 API and do not run a live server compatibility check.
## Features ## Features

View File

@@ -1,23 +1,29 @@
{ {
"metropolis": {
"id": "metropolis", "id": "metropolis",
"title": "Metropolis", "title": "Metropolis",
"description": "A Paper 26.2/26.3 city-planning plugin that turns player onboarding into procedural urban growth with protected UUID-owned homes, roads, reservations, and configurable infrastructure.", "description": "City manager that automatically creates protected home plots and roads as players join a Minecraft server.",
"docId": "Metropolis", "docId": "Metropolis",
"headerImage": null,
"platforms": [
{ "name": "Minecraft", "info": "Paper server plugin" }
],
"image": { "image": {
"url": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.", "url": "/projects/metropolis/logo.png",
"alt": "Pixel-art aerial view of a Minecraft city with player home plots and connecting roads", "alt": "Metropolis Minecraft plugin logo",
"prompt": "Pixel-art aerial view of a growing Minecraft city planned in a square grid, small player home plots connected by pale roads, a protected central spawn, blocky green landscape, subtle WorldGuard-inspired boundary lines, warm daylight, clean game-plugin thumbnail composition, no words or logos.", "width": 1024,
"width": 0, "height": 1024,
"height": 0,
"backgroundClassName": "bg-surface" "backgroundClassName": "bg-surface"
}, },
"url": "/projects/metropolis", "url": "/projects/metropolis",
"tags": ["open-source"], "tags": ["Open-Source", "Game", "Minecraft"],
"source": { "source": {
"type": "github", "type": "github",
"url": "https://github.com/headhunter45/Metropolis" "url": "https://github.com/headhunter45/Metropolis"
}, },
"feedback": { "feedback": {
"type": "github-issues",
"url": "https://github.com/headhunter45/Metropolis/issues" "url": "https://github.com/headhunter45/Metropolis/issues"
} }
} }
}

View File

@@ -1,5 +1,8 @@
# Metropolis # Metropolis
The project targets Paper 26.2 and 26.3; Gradle and CI compile against Paper
API 26.2, and CI does not run a live server compatibility check.
Metropolis turns player onboarding into city planning. As players arrive, the Paper plugin finds the next available plot, builds the surrounding roads and infrastructure, and extends a WorldGuard-protected city that feels designed rather than randomly generated. Metropolis turns player onboarding into city planning. As players arrive, the Paper plugin finds the next available plot, builds the surrounding roads and infrastructure, and extends a WorldGuard-protected city that feels designed rather than randomly generated.
The project combines procedural spatial logic, persistent UUID-based ownership, configurable construction rules, WorldEdit-assisted reservations, and a command suite for acquiring and selecting multiple homes. Home limits and plot sizes can be assigned per user or through permission-manager groups using configurable permission tiers. Reservation owners and members can teleport to their reserved plots, with explicit selection when they belong to multiple reservations. Active-home selections persist across reconnects and server restarts. It is also a modernization case study: a legacy plugin brought forward to Paper 26.2 and 26.3 with Gradle, focused unit tests, and opt-in live-server integration tests. Bukkit and Spigot servers are outside the support target. The project combines procedural spatial logic, persistent UUID-based ownership, configurable construction rules, WorldEdit-assisted reservations, and a command suite for acquiring and selecting multiple homes. Home limits and plot sizes can be assigned per user or through permission-manager groups using configurable permission tiers. Reservation owners and members can teleport to their reserved plots, with explicit selection when they belong to multiple reservations. Active-home selections persist across reconnects and server restarts. It is also a modernization case study: a legacy plugin brought forward to Paper 26.2 and 26.3 with Gradle, focused unit tests, and opt-in live-server integration tests. Bukkit and Spigot servers are outside the support target.

114
docs/projects.schema.json Normal file
View File

@@ -0,0 +1,114 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Projects",
"description": "Project metadata keyed by the project route slug.",
"type": "object",
"patternProperties": {
"^[a-z0-9]+(?:-[a-z0-9]+)*$": { "$ref": "#/definitions/project" }
},
"additionalProperties": false,
"definitions": {
"image": {
"type": "object",
"required": ["url", "width", "height"],
"properties": {
"url": {
"type": "string",
"description": "Public asset path or remote image URL. A blank header image URL falls back to image.",
"pattern": "^(\\s*|/[^/].*|https?://.+)$"
},
"alt": { "type": "string" },
"prompt": { "type": "string" },
"name": { "type": "string" },
"width": { "type": "integer", "minimum": 1 },
"height": { "type": "integer", "minimum": 1 },
"backgroundClassName": { "type": "string" }
},
"additionalProperties": false
},
"projectLink": {
"type": "object",
"required": ["url"],
"properties": {
"type": { "type": "string" },
"url": { "type": "string", "pattern": "^(\\s*|https?://.+)$" }
},
"additionalProperties": false
},
"platform": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string", "pattern": "\\S" },
"info": { "type": "string" },
"links": {
"type": "array",
"items": {
"type": "object",
"required": ["label", "url"],
"properties": {
"label": { "type": "string", "pattern": "\\S" },
"url": {
"type": "string",
"format": "uri",
"pattern": "^https?://.+"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"project": {
"type": "object",
"required": ["id", "title", "description", "tags"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
"title": { "type": "string", "pattern": "\\S" },
"description": { "type": "string", "pattern": "\\S" },
"docId": { "type": "string", "minLength": 1 },
"image": { "$ref": "#/definitions/image" },
"headerImage": {
"description": "Optional detail-page logo override. Omit, set to null, or use a blank URL to fall back to image. Listing cards continue to use image.",
"anyOf": [{ "$ref": "#/definitions/image" }, { "type": "null" }]
},
"status": { "type": "string" },
"platforms": {
"type": "array",
"items": { "$ref": "#/definitions/platform" }
},
"url": {
"type": "string",
"pattern": "^/projects/[a-z0-9]+(?:-[a-z0-9]+)*$"
},
"tags": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true
},
"source": {
"allOf": [
{ "$ref": "#/definitions/projectLink" },
{ "required": ["type"] }
]
},
"feedback": { "$ref": "#/definitions/projectLink" },
"repository": {
"type": "string",
"format": "uri",
"pattern": "^https?://.+"
},
"features": {
"type": "array",
"items": { "type": "string", "minLength": 1 }
},
"technology": {
"type": "array",
"items": { "type": "string", "minLength": 1 }
}
},
"additionalProperties": false
}
}
}