@@ -2,8 +2,10 @@ package ch.pontius.swissqr.api.cli
2
2
3
3
import ch.pontius.swissqr.api.db.ListStore
4
4
import ch.pontius.swissqr.api.model.access.Access
5
+ import ch.pontius.swissqr.api.model.users.Token
5
6
import ch.pontius.swissqr.api.model.users.TokenId
6
7
import ch.pontius.swissqr.api.model.users.User
8
+ import ch.pontius.swissqr.api.model.users.UserId
7
9
import com.github.ajalt.clikt.core.CliktCommand
8
10
import com.github.ajalt.clikt.core.NoOpCliktCommand
9
11
import com.github.ajalt.clikt.core.subcommands
@@ -13,6 +15,12 @@ import com.github.ajalt.clikt.parameters.options.option
13
15
import com.github.ajalt.clikt.parameters.options.required
14
16
import com.jakewharton.picnic.Table
15
17
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.*
16
24
17
25
/* *
18
26
* A collection of [CliktCommand]s to query and manipulate the [Access] logs.
@@ -23,19 +31,13 @@ import com.jakewharton.picnic.table
23
31
class LogCommand (private val logStore : ListStore <Access >) : NoOpCliktCommand(name = " log" ) {
24
32
25
33
init {
26
- this .subcommands(ListLogsCommand ())
34
+ this .subcommands(SummaryCommand (), ListLogsCommand ())
27
35
}
28
36
29
37
/* * 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
+ )
39
41
40
42
/* *
41
43
* Tabulates the given [Iterable] of [User] objects.
@@ -62,7 +64,94 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
62
64
}
63
65
64
66
/* *
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.
66
155
*/
67
156
inner class ListLogsCommand : CliktCommand (name = " list" , help = " Lists all access log entries." ) {
68
157
@@ -74,20 +163,24 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
74
163
.convert { it.toInt() }
75
164
76
165
/* * 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 }
79
168
.default(Long .MIN_VALUE )
80
169
81
170
/* * 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) ." )
83
172
.convert { it.toLong() }
84
173
.default(Long .MAX_VALUE )
85
174
86
175
/* * 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) ." )
88
177
.convert { it.toInt() }
89
178
.default(100 )
90
179
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
+
91
184
override fun run () {
92
185
val logs = this @LogCommand.logStore.filter {
93
186
var match = false
@@ -105,7 +198,14 @@ class LogCommand(private val logStore: ListStore<Access>) : NoOpCliktCommand(nam
105
198
}
106
199
match
107
200
}.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
+ }
109
209
}
110
210
}
111
211
}
0 commit comments