gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

✨ (dev): Seed richer demo data and skip onboarding on dev login

23a33e9c4794eaf15ce6547ab896d9ef9674fcd7 · Miggi · 2026-07-11T08:51:30Z

Changes

5 files changed, +200 -2

MODIFY README.md +7 -1
diff --git a/README.md b/README.md
index 06e97e5..0a08ebb 100644
--- a/README.md
+++ b/README.md
@@ -207,9 +207,15 @@
207 207
208 208 Two `%dev`-only flags are set in `application.properties` (both default `false` in all other profiles):
209 209
210 -- `gitshark.dev.seed-data=true` — on startup, `DevDataSeeder` idempotently creates user `alice` owning a public repository `demo` with one commit (`README.md`, "Initial commit"). A fresh dev instance is never empty.
210 +- `gitshark.dev.seed-data=true` — on startup, `DevDataSeeder` idempotently creates user `alice` owning a public repository `demo` with a Markdown README on `main`, an open merge request (`feature` → `main`, with a sample review comment) and four issues in different states with short and long Markdown descriptions. A fresh dev instance is never empty.
211 211 - `gitshark.dev.adopt-username=true` — Keycloak Dev Services mint a fresh OIDC subject on every run, so the seeded `alice` row would otherwise collide on login. With this flag, an unknown subject whose username matches an existing account re-keys that account to the new subject instead of rejecting the login. **Never enabled in production** (re-keying from a username claim is an account-takeover vector).
212 212
213 +Dev/test Keycloak imports `src/main/resources/quarkus-realm.json` (users `alice`/`alice` and
214 +`bob`/`bob`, client `quarkus-app` with the `profile` and `email` client scopes). The app requests
215 +`quarkus.oidc.authentication.scopes=profile,email`, so the ID token carries `preferred_username`,
216 +`name` and `email` — logging in as `alice` adopts the seeded account directly and skips the
217 +onboarding page.
218 +
213 219 ## Persisted data
214 220
215 221 | Store | What |
MODIFY src/main/java/de/workaround/dev/DevDataSeeder.java +86 -1
diff --git a/src/main/java/de/workaround/dev/DevDataSeeder.java b/src/main/java/de/workaround/dev/DevDataSeeder.java
index a84073d..70bf64e 100644
--- a/src/main/java/de/workaround/dev/DevDataSeeder.java
+++ b/src/main/java/de/workaround/dev/DevDataSeeder.java
@@ -8,8 +8,10 @@
8 8 import org.jboss.logging.Logger;
9 9
10 10 import de.workaround.git.GitRepositoryService;
11 +import de.workaround.git.IssueService;
11 12 import de.workaround.git.MergeRequestCommentService;
12 13 import de.workaround.git.MergeRequestService;
14 +import de.workaround.model.Issue;
13 15 import de.workaround.model.MergeRequest;
14 16 import de.workaround.model.Repository;
15 17 import de.workaround.model.User;
@@ -49,6 +51,9 @@
49 51 @Inject
50 52 MergeRequest.Repo mergeRequestRepo;
51 53
54 + @Inject
55 + IssueService issueService;
56 +
52 57 @ConfigProperty(name = "gitshark.dev.seed-data", defaultValue = "false")
53 58 boolean enabled;
54 59
@@ -92,6 +97,59 @@
92 97 }
93 98
94 99 seedMergeRequest(alice, demo, bare);
100 + seedIssues(alice, demo);
101 + }
102 +
103 + /**
104 + * Ensures a handful of demo issues exist so the issue list, status filters and Markdown rendering
105 + * have something to show locally. Idempotent: seeded only while the repository has no issues at all.
106 + */
107 + private void seedIssues(User alice, Repository demo)
108 + {
109 + if (!issueService.list(demo).isEmpty())
110 + {
111 + return;
112 + }
113 + issueService.create(alice, demo, "Add a CONTRIBUTING guide",
114 + "A short note on how to propose changes would help newcomers.");
115 + Issue inDevelopment = issueService.create(alice, demo, "Render Markdown in issue descriptions",
116 + """
117 + Issue descriptions support **Markdown**, so this issue shows the common elements in one place.
118 +
119 + ## Motivation
120 +
121 + A demo instance should let you see at a glance how formatted text renders:
122 +
123 + - **bold** and *italic* inline styles
124 + - [links](https://example.com) and `inline code`
125 + - nested lists
126 + - like this one
127 +
128 + ## Proposed approach
129 +
130 + 1. Parse the description with the same renderer the README uses.
131 + 2. Sanitize the resulting HTML.
132 + 3. Cache the rendered fragment alongside the issue.
133 +
134 + > Rendering and sanitizing must stay in sync — an unsanitized renderer would be an XSS hole.
135 +
136 + ```java
137 + String html = markdown.render(issue.description);
138 + ```
139 +
140 + This description is intentionally long so list truncation and detail pages are easy to check.""");
141 + Issue done = issueService.create(alice, demo, "Seed a demo repository on dev startup",
142 + """
143 + Done — a freshly started dev instance seeds `alice/demo` automatically.
144 +
145 + ```bash
146 + ./mvnw quarkus:dev
147 + ```""");
148 + issueService.updateStatus(alice, inDevelopment, Issue.Status.IN_DEVELOPMENT);
149 + issueService.updateStatus(alice, done, Issue.Status.DONE);
150 + issueService.create(alice, demo, "Show an empty state on the issues page",
151 + "When a repository has no issues yet, the list should explain how to create the first one.");
152 + LOG.infof("Seeded demo issues on %s/%s", alice.username, demo.name);
95 153 }
96 154
97 155 /**
@@ -179,7 +237,34 @@
179 237 try (Git git = Git.cloneRepository().setURI(barePath.toUri().toString()).setDirectory(work.toFile()).call())
180 238 {
181 239 Files.writeString(work.resolve("README.md"),
182 - "# demo\n\nDemo repository seeded for local development.\n");
240 + """
241 + # demo
242 +
243 + Demo repository seeded for local development. It exists so a freshly started
244 + dev instance has something to browse: a README, issues, and an open merge request.
245 +
246 + ## What's in here
247 +
248 + - This `README.md` on `main`
249 + - An `OVERVIEW.md` waiting on the `feature` branch (see the open merge request)
250 + - A few issues in different states with Markdown descriptions
251 +
252 + ## Try it
253 +
254 + Clone the repository and push a change:
255 +
256 + ```bash
257 + git clone http://localhost:8080/alice/demo.git
258 + cd demo
259 + git commit --allow-empty -m "Test commit"
260 + git push
261 + ```
262 +
263 + ## Notes
264 +
265 + Everything in this repository is seeded by `DevDataSeeder` on startup and only
266 + when missing, so restarts never duplicate data.
267 + """);
183 268 git.add().addFilepattern(".").call();
184 269 git.commit().setMessage("Initial commit").setSign(false)
185 270 .setAuthor(DEMO_USER, DEMO_USER + "@demo.local")
MODIFY src/main/resources/application.properties +3 -0
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3a1142d..231e1c4 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -23,6 +23,9 @@
23 23 %dev,test.quarkus.oidc.client-id=quarkus-app
24 24 %dev,test.quarkus.oidc.credentials.secret=secret
25 25 quarkus.oidc.application-type=web-app
26 +# Without these scopes the ID token carries only the subject: no preferred_username (dev
27 +# adopt-username can never match, every login lands on onboarding), no name, no email.
28 +quarkus.oidc.authentication.scopes=profile,email
26 29 # No RP-Initiated Logout: kanidm does not advertise an end_session_endpoint, and setting
27 30 # quarkus.oidc.logout.path makes Quarkus validate that endpoint at startup and abort boot when
28 31 # it is missing. Logout is therefore local session only (handled by the app).
ADD src/main/resources/quarkus-realm.json +61 -0
diff --git a/src/main/resources/quarkus-realm.json b/src/main/resources/quarkus-realm.json
new file mode 100644
index 0000000..8b4dde6
--- /dev/null
+++ b/src/main/resources/quarkus-realm.json
@@ -0,0 +1,61 @@
1 +{
2 + "realm": "quarkus",
3 + "enabled": true,
4 + "sslRequired": "none",
5 + "registrationAllowed": false,
6 + "users": [
7 + {
8 + "username": "alice",
9 + "enabled": true,
10 + "firstName": "Alice",
11 + "lastName": "Demo",
12 + "email": "alice@demo.local",
13 + "emailVerified": true,
14 + "credentials": [
15 + {
16 + "type": "password",
17 + "value": "alice",
18 + "temporary": false
19 + }
20 + ]
21 + },
22 + {
23 + "username": "bob",
24 + "enabled": true,
25 + "firstName": "Bob",
26 + "lastName": "Demo",
27 + "email": "bob@demo.local",
28 + "emailVerified": true,
29 + "credentials": [
30 + {
31 + "type": "password",
32 + "value": "bob",
33 + "temporary": false
34 + }
35 + ]
36 + }
37 + ],
38 + "clients": [
39 + {
40 + "clientId": "quarkus-app",
41 + "enabled": true,
42 + "protocol": "openid-connect",
43 + "publicClient": false,
44 + "secret": "secret",
45 + "standardFlowEnabled": true,
46 + "directAccessGrantsEnabled": true,
47 + "redirectUris": [
48 + "*"
49 + ],
50 + "webOrigins": [
51 + "*"
52 + ],
53 + "defaultClientScopes": [
54 + "basic",
55 + "profile",
56 + "email"
57 + ],
58 + "optionalClientScopes": []
59 + }
60 + ]
61 +}
MODIFY src/test/java/de/workaround/dev/DevDataSeederTest.java +43 -0
diff --git a/src/test/java/de/workaround/dev/DevDataSeederTest.java b/src/test/java/de/workaround/dev/DevDataSeederTest.java
index c69230f..b50f1f3 100644
--- a/src/test/java/de/workaround/dev/DevDataSeederTest.java
+++ b/src/test/java/de/workaround/dev/DevDataSeederTest.java
@@ -1,5 +1,6 @@
1 1 package de.workaround.dev;
2 2
3 +import java.nio.charset.StandardCharsets;
3 4 import java.nio.file.Path;
4 5 import java.util.List;
5 6
@@ -7,7 +8,9 @@
7 8
8 9 import de.workaround.git.GitBrowseService;
9 10 import de.workaround.git.GitRepositoryService;
11 +import de.workaround.git.IssueService;
10 12 import de.workaround.git.MergeRequestCommentService;
13 +import de.workaround.model.Issue;
11 14 import de.workaround.model.MergeRequest;
12 15 import de.workaround.model.Repository;
13 16 import io.quarkus.test.junit.QuarkusTest;
@@ -35,6 +38,9 @@
35 38 @Inject
36 39 MergeRequestCommentService comments;
37 40
41 + @Inject
42 + IssueService issues;
43 +
38 44 @Test
39 45 void seedsAliceWithDemoRepoContainingACommit()
40 46 {
@@ -85,4 +91,41 @@
85 91 "re-seeding must not add duplicate commits to the feature branch");
86 92 }
87 93
94 + @Test
95 + void seedsAReadmeWithRicherMarkdown()
96 + {
97 + seeder.seed();
98 + Repository demo = repositories.find("alice", "demo").orElseThrow();
99 +
100 + GitBrowseService.BlobView readme = browse.blob(repositories.repositoryPath(demo), "main", "README.md")
101 + .orElseThrow(() -> new AssertionError("README.md missing on main"));
102 + String content = new String(readme.content(), StandardCharsets.UTF_8);
103 + assertTrue(content.contains("## "), "README must contain markdown section headings");
104 + assertTrue(content.contains("```"), "README must contain a fenced code block");
105 + assertTrue(content.contains("- "), "README must contain a list");
106 + }
107 +
108 + @Test
109 + void seedsIssuesWithVariedMarkdownDescriptionsAndIsIdempotent()
110 + {
111 + seeder.seed();
112 + Repository demo = repositories.find("alice", "demo").orElseThrow();
113 +
114 + List<Issue> seeded = issues.list(demo);
115 + assertEquals(4, seeded.size(), "four demo issues must be seeded");
116 + assertTrue(seeded.stream().anyMatch(i -> i.description != null && i.description.length() > 400),
117 + "one issue must carry a long markdown description");
118 + assertTrue(seeded.stream().anyMatch(i -> i.description != null && i.description.length() < 120),
119 + "one issue must carry a short description");
120 + assertTrue(seeded.stream().anyMatch(i -> i.description != null && i.description.contains("```")),
121 + "one issue description must contain a fenced code block");
122 + assertTrue(seeded.stream().anyMatch(i -> i.status == Issue.Status.DONE),
123 + "one issue must already be done so status filters are demoable");
124 + assertTrue(seeded.stream().anyMatch(i -> i.status == Issue.Status.IN_DEVELOPMENT),
125 + "one issue must be in development");
126 +
127 + seeder.seed();
128 + assertEquals(4, issues.list(demo).size(), "re-seeding must not duplicate issues");
129 + }
130 +
88 131 }

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog