-
Notifications
You must be signed in to change notification settings - Fork 7
/
admin.java
executable file
·177 lines (155 loc) · 5.11 KB
/
admin.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17
//JAVAC_OPTIONS -parameters
//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager
//DEPS io.quarkus.platform:quarkus-bom:3.6.6@pom
//DEPS org.jdbi:jdbi3-bom:3.43.0@pom
//DEPS io.quarkus:quarkus-resteasy-qute
//DEPS io.quarkus:quarkus-agroal
//DEPS org.duckdb:duckdb_jdbc:0.9.2
//DEPS org.jdbi:jdbi3-core
//DEPS org.apache.commons:commons-text:1.10.0
//Q:CONFIG quarkus.datasource.db-kind=other
//Q:CONFIG quarkus.datasource.jdbc.driver=org.duckdb.DuckDBDriver
//Q:CONFIG quarkus.datasource.jdbc.url=jdbc:duckdb:
import java.net.URI;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import org.apache.commons.text.StringEscapeUtils;
import org.jdbi.v3.core.Jdbi;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
/**
* Party like it's 1999 PHP, but with Quarkus and Java… And why not: Architecture is for everyone and even
* "it's good enough" is an applicable architecture style for some use cases. Happy coding.
*
* @author Michael J. Simons
*/
@Path("/")
public class admin {
private static final String INDEX_HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>all.csv</title>
</head>
<body>
<h1>Add a new book</h1>
<form method="post">
<label for="author">Author</label> <input type="text" list="authors" id="author" name="author"><br>
<datalist id="authors">
${authors}
</datalist>
<label for="title">Title</label> <input type="text" id="title" name="title"><br>
<label for="type">Type</label> 
<select name="type" id="type">
<option value="R">Roman / Prose</option>
<option value="S">Sachbuch / Non-fiction</option>
</select><br>
<label for="state">State</label> 
<select name="state" id="state">
<option value="R">Read</option>
<option value="U">Unread</option>
</select><br>
<input type="submit">
</form>
<h2>All books</h2>
<table>
<thead>
<tr>
<th>Author(s)</th>
<th>Title</th>
</tr>
</thead>
<tbody>
${books}
</tbody>
</table>
</body>
</html>
""";
private final Jdbi db;
public static void main(String[] args) {
Quarkus.run(args);
}
@Inject
public admin(DataSource dataSource) {
db = Jdbi.create(dataSource);
}
public void onStart(@Observes StartupEvent ev) {
db.useHandle(handle -> handle.execute("""
CREATE TABLE books
AS SELECT *
FROM read_csv('all.csv', header=true, auto_detect=true)
"""
));
}
@GET
@Produces(MediaType.TEXT_HTML)
public String index() {
var authors = db.withHandle(handle -> handle
.createQuery("""
WITH src AS (SELECT unnest(split(Author,'&')) Author FROM books)
SELECT DISTINCT trim(Author) AS Author
FROM src
ORDER BY Author ASC
""")
.map(r -> r.getColumn("Author", String.class))
.map(StringEscapeUtils::escapeHtml4)
.map("<option value=\"%s\">%n"::formatted)
.stream()
.collect(Collectors.joining(" "))
.trim()
);
var books = db.withHandle(handle -> handle
.createQuery("SELECT Author, Title FROM books ORDER BY author collate de asc, title collate de ASC")
.map(r -> "<tr><td>%s</td><td>%s</td></tr>%n"
.formatted(
StringEscapeUtils.escapeHtml4(r.getColumn("Author", String.class)),
StringEscapeUtils.escapeHtml4(r.getColumn("Title", String.class))
))
.stream()
.collect(Collectors.joining(" "))
.trim()
);
return INDEX_HTML
.replace("${authors}", authors)
.replace("${books}", books);
}
@POST
@Consumes("application/x-www-form-urlencoded")
public Response save(@Context UriInfo uriInfo, MultivaluedMap<String, String> formdata) {
db.useHandle(handle -> handle.createUpdate("""
INSERT INTO books (Author, Title, Type, State)
VALUES (:Author, :Title, :Type, :State)
""")
.bind("Author", formdata.get("author").get(0))
.bind("Title", formdata.get("title").get(0))
.bind("Type", formdata.get("type").get(0))
.bind("State", formdata.get("state").get(0))
.execute());
return Response.seeOther(uriInfo.resolve(URI.create("/"))).build();
}
public void onStop(@Observes ShutdownEvent ev) {
db.useHandle(handle -> handle.execute("""
COPY (SELECT * FROM books ORDER BY author COLLATE de ASC, title COLLATE de ASC)
TO 'all.csv'
WITH (header true);
"""
));
}
}