Skip to content

Commit e42b8de

Browse files
committed
Added CLI command to create per token access summaries.
1 parent c5a5107 commit e42b8de

File tree

1 file changed

+116
-16
lines changed

1 file changed

+116
-16
lines changed

src/main/kotlin/ch/pontius/swissqr/api/cli/LogCommand.kt

+116-16
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package ch.pontius.swissqr.api.cli
22

33
import ch.pontius.swissqr.api.db.ListStore
44
import ch.pontius.swissqr.api.model.access.Access
5+
import ch.pontius.swissqr.api.model.users.Token
56
import ch.pontius.swissqr.api.model.users.TokenId
67
import ch.pontius.swissqr.api.model.users.User
8+
import ch.pontius.swissqr.api.model.users.UserId
79
import com.github.ajalt.clikt.core.CliktCommand
810
import com.github.ajalt.clikt.core.NoOpCliktCommand
911
import com.github.ajalt.clikt.core.subcommands
@@ -13,6 +15,12 @@ import com.github.ajalt.clikt.parameters.options.option
1315
import com.github.ajalt.clikt.parameters.options.required
1416
import com.jakewharton.picnic.Table
1517
import com.jakewharton.picnic.table
18+
import java.nio.file.Files
19+
import java.nio.file.Path
20+
import java.nio.file.Paths
21+
import java.nio.file.StandardOpenOption
22+
import java.text.SimpleDateFormat
23+
import java.util.*
1624

1725
/**
1826
* A collection of [CliktCommand]s to query and manipulate the [Access] logs.
@@ -23,19 +31,13 @@ import com.jakewharton.picnic.table
2331
class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(name = "log") {
2432

2533
init {
26-
this.subcommands(ListLogsCommand())
34+
this.subcommands(SummaryCommand(), ListLogsCommand())
2735
}
2836

2937
/** List of defined aliases for this [UserCommand]. */
30-
override fun aliases(): Map<String, List<String>> {
31-
return mapOf(
32-
"ls" to listOf("list"),
33-
"delete" to listOf("invalidate"),
34-
"remove" to listOf("invalidate"),
35-
"drop" to listOf("invalidate"),
36-
"add" to listOf("create")
37-
)
38-
}
38+
override fun aliases(): Map<String, List<String>> = mapOf(
39+
"ls" to listOf("list")
40+
)
3941

4042
/**
4143
* Tabulates the given [Iterable] of [User] objects.
@@ -62,7 +64,94 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
6264
}
6365

6466
/**
65-
* [CliktCommand] to list all available [User]s.
67+
* Tabulates the given [Iterable] of [User] objects.
68+
*
69+
* @param users [Iterable] of [User] objects
70+
* @return Resulting [Table]
71+
*/
72+
private fun exportAccessLogs(out: Path, users: Iterable<Access?>) = Files.newBufferedWriter(out, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE).use {
73+
it.write("tokenId,source,path,method,status,timestamp")
74+
it.newLine()
75+
for (user in users) {
76+
if (user != null) {
77+
it.write("${user.tokenId},${user.ip},${user.path},${user.method},${user.status},${user.timestamp}")
78+
}
79+
it.newLine()
80+
}
81+
it.flush()
82+
}
83+
84+
/**
85+
* [CliktCommand] to summarize [Access] logs.
86+
*/
87+
inner class SummaryCommand : CliktCommand(name = "summary", help = "Creates a summary of all the logs.") {
88+
/** Flag that can be set to only list active tokens. */
89+
private val status: Int? by option("-s", "--status", help = "Only lists entries that match the given status.")
90+
.convert { it.toInt() }
91+
92+
/** Flag that can be set to only list active tokens. */
93+
private val after: Long by option("-a", "--after", help = "Only lists entries that happened after the given date (YYYY-MM-DD).")
94+
.convert { SimpleDateFormat("yyyy-mm-dd").parse(it).time }
95+
.default(Long.MIN_VALUE)
96+
97+
/** Flag that can be set to only list active tokens. */
98+
private val before: Long by option("-b", "--before", help = "Only lists entries that happened before the given date (YYYY-MM-DD).")
99+
.convert { it.toLong() }
100+
.default(Long.MAX_VALUE)
101+
102+
/** Flag that can be set to only list active tokens. */
103+
private val output: Path? by option("-o", "--out", help = "Routes the output of the command into the given output file.")
104+
.convert { Paths.get(it) }
105+
106+
override fun run() {
107+
val summary = mutableMapOf<TokenId,Long>()
108+
val logs = this@LogCommand.logStore.filterNotNull().filter {
109+
var match = (it.timestamp > this.after && it.timestamp < this.before)
110+
if (this.status != null) {
111+
match = match && (it.status == this.status)
112+
}
113+
match
114+
}.forEach {
115+
summary.compute(it.tokenId) { k, v ->
116+
val res = v ?: 0L
117+
res + 1
118+
}
119+
}
120+
121+
/* Print or export logs. */
122+
if (this.output == null) {
123+
println(table {
124+
cellStyle {
125+
border = true
126+
paddingLeft = 1
127+
paddingRight = 1
128+
}
129+
header {
130+
row("tokenId", "accesses")
131+
}
132+
body {
133+
summary.forEach {
134+
row(it.key, it.value)
135+
}
136+
}
137+
})
138+
} else {
139+
Files.newBufferedWriter(this.output!!, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE).use { writer ->
140+
writer.write("tokenId,accesses")
141+
writer.newLine()
142+
summary.forEach {
143+
writer.write("${it.key},${it.value}")
144+
writer.newLine()
145+
}
146+
writer.flush()
147+
}
148+
println("${summary.size} summaries exported to ${this.output}")
149+
}
150+
}
151+
}
152+
153+
/**
154+
* [CliktCommand] to list all available [Access] logs.
66155
*/
67156
inner class ListLogsCommand : CliktCommand(name = "list", help = "Lists all access log entries.") {
68157

@@ -74,20 +163,24 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
74163
.convert { it.toInt() }
75164

76165
/** Flag that can be set to only list active tokens. */
77-
private val after: Long by option("-a", "--after", help = "Only lists entries that happened after the given timestamp.")
78-
.convert { it.toLong() }
166+
private val after: Long by option("-a", "--after", help = "Only lists entries that happened after the given date (YYYY-MM-DD).")
167+
.convert { SimpleDateFormat("yyyy-mm-dd").parse(it).time }
79168
.default(Long.MIN_VALUE)
80169

81170
/** Flag that can be set to only list active tokens. */
82-
private val before: Long by option("-b", "--before", help = "Only lists entries that happened before the given timestamp.")
171+
private val before: Long by option("-b", "--before", help = "Only lists entries that happened before the given date (YYYY-MM-DD).")
83172
.convert { it.toLong() }
84173
.default(Long.MAX_VALUE)
85174

86175
/** Flag that can be set to only list active tokens. */
87-
private val limit: Int by option("-l", "--limit", help = "Only lists entries that happened before the given timestamp.")
176+
private val limit: Int by option("-l", "--limit", help = "Limits the result set to the given number of entries (default = 100).")
88177
.convert { it.toInt() }
89178
.default(100)
90179

180+
/** Flag that can be set to only list active tokens. */
181+
private val output: Path? by option("-o", "--out", help = "Routes the output of the command into the given output file.")
182+
.convert { Paths.get(it) }
183+
91184
override fun run() {
92185
val logs = this@LogCommand.logStore.filter {
93186
var match = false
@@ -105,7 +198,14 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
105198
}
106199
match
107200
}.takeLast(this.limit)
108-
println(this@LogCommand.tabulateAccessLogs(logs))
201+
202+
/* Print or export logs. */
203+
if (this.output == null) {
204+
println(this@LogCommand.tabulateAccessLogs(logs))
205+
} else {
206+
this@LogCommand.exportAccessLogs(this.output!!, logs)
207+
println("${logs.size} log entries exported to ${this.output}")
208+
}
109209
}
110210
}
111211
}

0 commit comments

Comments
 (0)