icfs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # See LICENSE.txt for licensing information.
7
+ #
8
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
9
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+
11
+ require 'rack'
12
+
13
+ module ICFS
14
+ module Demo
15
+
16
+ ##########################################################################
17
+ # Test authentication - Rack middleware
18
+ #
19
+ # @deprecated This is a horrible security implementation, DO NOT USE
20
+ # for anything other than testing.
21
+ #
22
+ class Auth
23
+
24
+
25
+ ###############################################
26
+ # New instance
27
+ #
28
+ # @param app [Object] The rack app
29
+ # @param api [Object] the ICFS API
30
+ #
31
+ def initialize(app, api)
32
+ @app = app
33
+ @api = api
34
+ end
35
+
36
+
37
+ ###############################################
38
+ # Handle the calls
39
+ #
40
+ def call(env)
41
+
42
+ # login
43
+ if env['PATH_INFO'] == '/login'.freeze
44
+ user = env['QUERY_STRING']
45
+ body = 'User set'
46
+
47
+ # set the cookie
48
+ rsp = Rack::Response.new( body, 200, {} )
49
+ rsp.set_cookie( 'icfs-user', {
50
+ value: user,
51
+ expires: Time.now + 24*60*60
52
+ })
53
+ return rsp.finish
54
+ end
55
+
56
+ # pull the username from the cookie
57
+ cookies = Rack::Request.new(env).cookies
58
+ user = cookies['icfs-user']
59
+ if !user
60
+ return [400, {'Content-Type' => 'text/plain'}, ['Login first'.freeze]]
61
+ end
62
+ @api.user = user
63
+ env['icfs'] = @api
64
+ return @app.call(env)
65
+
66
+ rescue ICFS::Error::NotFound, ICFS::Error::Value => err
67
+ return [400, {'Content-Type' => 'text/plain'}, [err.message]]
68
+ end # def call()
69
+
70
+ end # class ICFS::Demo::Auth
71
+
72
+ end # module ICFS::Demo
73
+
74
+ end # module ICFS
@@ -0,0 +1,59 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # See LICENSE.txt for licensing information.
7
+ #
8
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
9
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+
11
+ #
12
+ module ICFS
13
+
14
+
15
+ ##########################################################################
16
+ # Demonstration only files
17
+ module Demo
18
+
19
+ ##########################################################################
20
+ # Serve static files - Rack middleware
21
+ #
22
+ # @deprecated This is a horrible implementation, DO NOT USE
23
+ # for anything other than testing.
24
+ #
25
+ class Static
26
+
27
+ ###############################################
28
+ # New instance
29
+ #
30
+ # @param stat [Hash] maps path to Hash with :path and :mime
31
+ # @param app [Object] the next rack app
32
+ #
33
+ def initialize(app, stat)
34
+ @stat = stat
35
+ @app = app
36
+ end
37
+
38
+ # Process requests
39
+ def call(env)
40
+
41
+ # see if we have a static file to serve
42
+ st = @stat[env['PATH_INFO']]
43
+ if st
44
+ cont = File.read(st['path'])
45
+ head = {
46
+ 'Content-Type' => st['mime'],
47
+ 'Content-Length' => cont.bytesize.to_s
48
+ }
49
+ return [200, head, [cont]]
50
+ end
51
+
52
+ return @app.call(env)
53
+
54
+ end
55
+
56
+ end # class ICFS::Demo::Static
57
+
58
+ end # module ICFS::Demo
59
+ end # module ICFS
@@ -0,0 +1,38 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # See LICENSE.txt for licensing information.
7
+ #
8
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
9
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+
11
+ #
12
+ module ICFS
13
+ module Demo
14
+
15
+ ##########################################################################
16
+ # Set timezone - Rack middleware
17
+ #
18
+ # @deprecated This does nothing but set a static timezone. Do not use for
19
+ # anything other than testing.
20
+ #
21
+ class Timezone
22
+
23
+ # New instance
24
+ def initialize(app, tz)
25
+ @app = app
26
+ @tz = tz.freeze
27
+ end
28
+
29
+ # process requests
30
+ def call(env)
31
+ env['icfs.tz'] = @tz
32
+ @app.call(env)
33
+ end
34
+
35
+ end # class ICFS::Demo::Timezone
36
+
37
+ end # module ICFS::Demo
38
+ end # module ICFS
@@ -0,0 +1,83 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ #
13
+ module ICFS
14
+
15
+ ##########################################################################
16
+ # Shared Elasticsearch methods
17
+ #
18
+ module Elastic
19
+
20
+ private
21
+
22
+ ###############################################
23
+ # Read an item
24
+ #
25
+ # @param ix [Symbol] the index to read
26
+ # @param id [String] the id to read
27
+ #
28
+ # @return [String] JSON encoded object
29
+ #
30
+ def _read(ix, id)
31
+ url = '%s/_doc/%s/_source'.freeze % [ @map[ix], CGI.escape(id)]
32
+ resp = @es.run_request(:get, url, ''.freeze, {})
33
+ if resp.status == 404
34
+ return nil
35
+ elsif !resp.success?
36
+ raise('Elasticsearch read failed'.freeze)
37
+ end
38
+ return resp.body
39
+ end # def _read()
40
+
41
+
42
+ ###############################################
43
+ # Write an item
44
+ #
45
+ # @param ix [Symbol] the index to write
46
+ # @param id [String] the id to write
47
+ # @param item [String] JSON encoded object to write
48
+ #
49
+ def _write(ix, id, item)
50
+ url = '%s/_doc/%s'.freeze % [ @map[ix], CGI.escape(id)]
51
+ head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
52
+ resp = @es.run_request(:put, url, item, head)
53
+ if !resp.success?
54
+ raise('Elasticsearch index failed'.freeze)
55
+ end
56
+ end # def _write()
57
+
58
+
59
+ public
60
+
61
+
62
+ ###############################################
63
+ # Create ES indices
64
+ # @param maps [Hash] symbol to Elasticsearch mapping
65
+ #
66
+ def create(maps)
67
+ head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
68
+ maps.each do |ix, map|
69
+ url = @map[ix]
70
+ resp = @es.run_request(:put, url, map, head)
71
+ if !resp.success?
72
+ puts 'URL: %s' % url
73
+ puts map
74
+ puts resp.body
75
+ raise('Elasticsearch index create failed: %s'.freeze % ix.to_s)
76
+ end
77
+ end
78
+ end # def create()
79
+
80
+
81
+ end # module ICFS::Elastic
82
+
83
+ end # module ICFS
data/lib/icfs/items.rb ADDED
@@ -0,0 +1,653 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ #
13
+ module ICFS
14
+
15
+
16
+ ##########################################################################
17
+ # Items
18
+ #
19
+ module Items
20
+
21
+ ##############################################################
22
+ # Base fields
23
+ ##############################################################
24
+
25
+ # ICFS version
26
+ FieldIcfs = {
27
+ method: :integer,
28
+ min: 1,
29
+ max: 1,
30
+ }.freeze
31
+
32
+
33
+ # Caseid
34
+ FieldCaseid = {
35
+ method: :string,
36
+ min: 1,
37
+ max: 32,
38
+ invalid: /[[:cntrl:][:space:]"\#$%'()*+\/;<=>?@\[\]\\^`{|}~]/.freeze,
39
+ }.freeze
40
+
41
+
42
+ # Title
43
+ FieldTitle = {
44
+ method: :string,
45
+ min: 1,
46
+ max: 128,
47
+ invalid: /[[:cntrl:]]/.freeze,
48
+ }.freeze
49
+
50
+
51
+ # Tag
52
+ # No control characters
53
+ # may not start with brackets or whitespace
54
+ FieldTag = {
55
+ method: :string,
56
+ min: 1,
57
+ max: 32,
58
+ invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
59
+ }.freeze
60
+
61
+
62
+ # Tag for Entry
63
+ FieldTagEntry = {
64
+ method: :string,
65
+ min: 1,
66
+ max: 32,
67
+ allowed: Set[
68
+ ICFS::TagAction,
69
+ ICFS::TagIndex,
70
+ ICFS::TagCase,
71
+ ].freeze,
72
+ invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
73
+ }.freeze
74
+
75
+
76
+ # Special tags
77
+ FieldTagSpecial = {
78
+ method: :string,
79
+ allowed: Set[
80
+ ICFS::TagNone,
81
+ ICFS::TagAction,
82
+ ICFS::TagIndex,
83
+ ICFS::TagCase,
84
+ ].freeze,
85
+ whitelist: true,
86
+ }.freeze
87
+
88
+
89
+ # Any tag, including empty
90
+ FieldTagAny = {
91
+ method: :any,
92
+ check: [
93
+ FieldTag,
94
+ FieldTagSpecial
95
+ ].freeze,
96
+ }.freeze
97
+
98
+
99
+ # a normal perm name
100
+ # No control characters
101
+ # may not start with square brackets, curly brackets, or whitespace
102
+ FieldPermNormal = {
103
+ method: :string,
104
+ min: 1,
105
+ max: 64,
106
+ invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
107
+ }.freeze
108
+
109
+
110
+ # A reserved case name
111
+ # square brackets
112
+ FieldPermReserve = {
113
+ method: :string,
114
+ allowed: Set[
115
+ ICFS::PermRead,
116
+ ICFS::PermWrite,
117
+ ICFS::PermManage,
118
+ ICFS::PermAction
119
+ ].freeze,
120
+ whitelist: true
121
+ }.freeze
122
+
123
+
124
+ # A global permission
125
+ # curly brackets, no control characters
126
+ FieldPermGlobal = {
127
+ method: :string,
128
+ min: 1,
129
+ max: 64,
130
+ valid: /^\{[^[:cntrl:]]+\}$/.freeze,
131
+ whitelist: true,
132
+ }.freeze
133
+
134
+
135
+ # A case perm
136
+ FieldPermCase = {
137
+ method: :any,
138
+ check: [
139
+ FieldPermNormal,
140
+ FieldPermReserve,
141
+ ].freeze
142
+ }.freeze
143
+
144
+
145
+ # Any perm
146
+ FieldPermAny = {
147
+ method: :any,
148
+ check: [
149
+ FieldPermNormal,
150
+ FieldPermReserve,
151
+ FieldPermGlobal,
152
+ ].freeze
153
+ }.freeze
154
+
155
+
156
+ # a user/group name
157
+ # No control characters
158
+ # no space, no punctuation except , - : _
159
+ FieldUsergrp = {
160
+ method: :string,
161
+ min: 3,
162
+ max: 32,
163
+ invalid: /[\x00-\x2b\x2e\x2f\x3b-\x40\x5b-\x5e\x60\x7b-\x7f]/.freeze,
164
+ }.freeze
165
+
166
+
167
+ # a hash
168
+ FieldHash = {
169
+ method: :string,
170
+ min: 64,
171
+ max: 64,
172
+ invalid: /[^0-9a-f]/.freeze
173
+ }.freeze
174
+
175
+
176
+ # Content
177
+ FieldContent = {
178
+ method: :string,
179
+ min: 1,
180
+ max: 8*1024,
181
+ invalid: /[^[:graph:][:space:]]/.freeze,
182
+ }.freeze
183
+
184
+
185
+ # a stat name
186
+ FieldStat = {
187
+ method: :string,
188
+ min: 1,
189
+ max: 32,
190
+ invalid: /[[:cntrl:]\t\r\n\v\f]|^_/.freeze,
191
+ }.freeze
192
+
193
+
194
+ # a filename
195
+ FieldFilename = {
196
+ method: :string,
197
+ min: 1,
198
+ max: 128,
199
+ invalid: /[[:cntrl:]\\\/]|^\./.freeze
200
+ }.freeze
201
+
202
+
203
+ ##############################################################
204
+ # Sub-item parts
205
+ ##############################################################
206
+
207
+ # Empty Tags
208
+ #
209
+ SubTagsEmpty = {
210
+ method: :array,
211
+ min: 1,
212
+ max: 1,
213
+ check: {
214
+ method: :equals,
215
+ check: ICFS::TagNone
216
+ }.freeze
217
+ }.freeze
218
+
219
+
220
+ # Entry Tags
221
+ #
222
+ SubTagsEntry = {
223
+ method: :array,
224
+ min: 1,
225
+ check: FieldTagEntry
226
+ }.freeze
227
+
228
+
229
+ # Tags
230
+ SubTagsNormal = {
231
+ method: :array,
232
+ min: 1,
233
+ check: FieldTag
234
+ }.freeze
235
+
236
+
237
+ # Tags
238
+ SubTags = {
239
+ method: :any,
240
+ check: [ SubTagsEmpty, SubTagsNormal ].freeze
241
+ }.freeze
242
+
243
+
244
+ # Grant
245
+ SubGrant = {
246
+ method: :hash,
247
+ required: {
248
+ 'perm' => FieldPermCase,
249
+ 'grant' => {
250
+ method: :array,
251
+ min: 1,
252
+ check: FieldUsergrp
253
+ }.freeze
254
+ }.freeze
255
+ }.freeze
256
+
257
+
258
+ # Access
259
+ SubAccess = {
260
+ method: :array,
261
+ min: 1,
262
+ check: SubGrant
263
+ }.freeze
264
+
265
+
266
+ # Case stats
267
+ SubCaseStats = {
268
+ method: :array,
269
+ min:1,
270
+ check: FieldStat,
271
+ }.freeze
272
+
273
+
274
+ # An item in a log
275
+ SubLogItem = {
276
+ method: :hash,
277
+ required: {
278
+ 'num' => Validate::IsIntPos,
279
+ 'hash' => FieldHash,
280
+ }.freeze
281
+ }.freeze
282
+
283
+
284
+ # Indexes
285
+ SubIndexes = {
286
+ method: :array,
287
+ min: 1,
288
+ check: Validate::IsIntPos,
289
+ }.freeze
290
+
291
+
292
+ # Perms
293
+ SubPerms = {
294
+ method: :array,
295
+ min: 1,
296
+ check: FieldPermAny
297
+ }.freeze
298
+
299
+
300
+ # Stats
301
+ SubStats = {
302
+ method: :array,
303
+ min: 1,
304
+ check: {
305
+ method: :hash,
306
+ required: {
307
+ "name" => FieldStat,
308
+ "value" => Validate::IsFloat,
309
+ "credit" => {
310
+ method: :array,
311
+ min: 1,
312
+ max: 32,
313
+ check: FieldUsergrp
314
+ }.freeze
315
+ }.freeze
316
+ }.freeze
317
+ }.freeze
318
+
319
+
320
+ # An old file
321
+ SubFileOld = {
322
+ method: :hash,
323
+ required: {
324
+ 'log' => Validate::IsIntPos,
325
+ 'num' => Validate::IsIntUns,
326
+ 'name' => FieldFilename,
327
+ }.freeze
328
+ }.freeze
329
+
330
+
331
+ # Case task
332
+ SubTaskCase = {
333
+ method: :hash,
334
+ required: {
335
+ 'assigned' => {
336
+ method: :string,
337
+ allowed: Set[ ICFS::UserCase ].freeze,
338
+ whitelist: true
339
+ }.freeze,
340
+ 'title' => FieldTitle,
341
+ 'status' => Validate::IsBoolean,
342
+ 'flag' => Validate::IsBoolean,
343
+ 'time' => Validate::IsIntPos,
344
+ 'tags' => SubTags
345
+ }.freeze
346
+ }.freeze
347
+
348
+
349
+ # Normal task
350
+ SubTaskNormal = {
351
+ method: :hash,
352
+ required: {
353
+ 'assigned' => FieldUsergrp,
354
+ 'title' => FieldTitle,
355
+ 'status' => Validate::IsBoolean,
356
+ 'flag' => Validate::IsBoolean,
357
+ 'time' => Validate::IsIntPos,
358
+ 'tags' => SubTags
359
+ }.freeze
360
+ }.freeze
361
+
362
+
363
+ # Tasks
364
+ SubTasks = {
365
+ method: :array,
366
+ min: 1,
367
+ 0 => SubTaskCase,
368
+ check: SubTaskNormal
369
+ }.freeze
370
+
371
+
372
+ # Case task
373
+ SubTaskEditCase = {
374
+ method: :hash,
375
+ required: {
376
+ 'assigned' => {
377
+ method: :string,
378
+ allowed: Set[ ICFS::UserCase ].freeze,
379
+ whitelist: true
380
+ }.freeze,
381
+ 'title' => FieldTitle,
382
+ 'status' => Validate::IsBoolean,
383
+ 'flag' => Validate::IsBoolean,
384
+ 'time' => Validate::IsIntPos,
385
+ }.freeze,
386
+ optional: {
387
+ 'tags' => SubTags
388
+ }.freeze
389
+ }.freeze
390
+
391
+
392
+ # Normal task
393
+ SubTaskEditNormal = {
394
+ method: :hash,
395
+ required: {
396
+ 'assigned' => FieldUsergrp,
397
+ 'title' => FieldTitle,
398
+ 'status' => Validate::IsBoolean,
399
+ 'flag' => Validate::IsBoolean,
400
+ 'time' => Validate::IsIntPos,
401
+ }.freeze,
402
+ optional: {
403
+ 'tags' => SubTags
404
+ }.freeze
405
+ }.freeze
406
+
407
+
408
+ # TasksEdit
409
+ SubTasksEdit = {
410
+ method: :array,
411
+ min: 1,
412
+ 0 => SubTaskEditCase,
413
+ check: SubTaskEditNormal,
414
+ }.freeze
415
+
416
+
417
+ # A new file
418
+ SubFileNew = {
419
+ method: :hash,
420
+ required: {
421
+ 'temp' => Validate::IsTempfile,
422
+ 'name' => FieldFilename,
423
+ }.freeze
424
+ }.freeze
425
+
426
+
427
+ ##############################################################
428
+ # Check Items
429
+ ##############################################################
430
+
431
+
432
+ # Case - Edit
433
+ ItemCaseEdit = {
434
+ method: :hash,
435
+ required: {
436
+ 'template' => Validate::IsBoolean,
437
+ 'status' => Validate::IsBoolean,
438
+ 'title' => FieldTitle,
439
+ 'access' => SubAccess
440
+ }.freeze,
441
+ optional: {
442
+ 'tags' => SubTags,
443
+ 'stats' => SubCaseStats,
444
+ }.freeze
445
+ }.freeze
446
+
447
+
448
+ # Entry - New only
449
+ ItemEntryNew = {
450
+ method: :hash,
451
+ required: {
452
+ 'caseid' => FieldCaseid,
453
+ 'title' => FieldTitle,
454
+ 'content' => FieldContent,
455
+ }.freeze,
456
+ optional: {
457
+ 'time' => Validate::IsIntPos,
458
+ 'tags' => SubTagsNormal,
459
+ 'index' => SubIndexes,
460
+ 'action' => Validate::IsIntPos,
461
+ 'perms' => SubPerms,
462
+ 'stats' => SubStats,
463
+ 'files' => {
464
+ method: :array,
465
+ min: 1,
466
+ check: SubFileNew
467
+ }.freeze
468
+ }.freeze
469
+ }.freeze
470
+
471
+
472
+ # Entry - Edit or New
473
+ ItemEntryEdit = {
474
+ method: :hash,
475
+ required: {
476
+ 'caseid' => FieldCaseid,
477
+ 'title' => FieldTitle,
478
+ 'content' => FieldContent,
479
+ }.freeze,
480
+ optional: {
481
+ 'entry' => Validate::IsIntPos,
482
+ 'time' => Validate::IsIntPos,
483
+ 'tags' => SubTagsEntry,
484
+ 'index' => SubIndexes,
485
+ 'action' => Validate::IsIntPos,
486
+ 'perms' => SubPerms,
487
+ 'stats' => SubStats,
488
+ 'files' => {
489
+ method: :array,
490
+ min: 1,
491
+ check: {
492
+ method: :any,
493
+ check: [ SubFileOld, SubFileNew ].freeze
494
+ }.freeze
495
+ }.freeze
496
+ }.freeze
497
+ }.freeze
498
+
499
+
500
+ # Action - Edit or New
501
+ ItemActionEdit = {
502
+ method: :hash,
503
+ required: {
504
+ 'tasks' => SubTasksEdit
505
+ }.freeze,
506
+ optional: {
507
+ 'action' => Validate::IsIntPos
508
+ }.freeze
509
+ }.freeze
510
+
511
+
512
+ # Index - Edit or New
513
+ ItemIndexEdit = {
514
+ method: :hash,
515
+ required: {
516
+ 'title' => FieldTitle,
517
+ 'content' => FieldContent,
518
+ }.freeze,
519
+ optional: {
520
+ 'index' => Validate::IsIntPos,
521
+ 'tags' => SubTags,
522
+ }.freeze
523
+ }.freeze
524
+
525
+
526
+ ##############################################################
527
+ # Recorded items
528
+ ##############################################################
529
+
530
+ # Case
531
+ ItemCase = {
532
+ method: :hash,
533
+ required: {
534
+ 'icfs' => FieldIcfs,
535
+ 'caseid' => FieldCaseid,
536
+ 'log' => Validate::IsIntPos,
537
+ 'template' => Validate::IsBoolean,
538
+ 'status' => Validate::IsBoolean,
539
+ 'title' => FieldTitle,
540
+ 'tags' => SubTags,
541
+ 'access' => SubAccess,
542
+ }.freeze,
543
+ optional: {
544
+ 'stats' => SubCaseStats,
545
+ }.freeze,
546
+ }.freeze
547
+
548
+
549
+ # Log
550
+ ItemLog = {
551
+ method: :hash,
552
+ required: {
553
+ 'icfs' => FieldIcfs,
554
+ 'caseid' => FieldCaseid,
555
+ 'log' => Validate::IsIntPos,
556
+ 'prev' => FieldHash,
557
+ 'time' => Validate::IsIntPos,
558
+ 'user' => FieldUsergrp,
559
+ 'entry' => SubLogItem,
560
+ }.freeze,
561
+ optional: {
562
+ 'index' => SubLogItem,
563
+ 'action' => SubLogItem,
564
+ 'case_hash' => FieldHash,
565
+ 'files_hash' => {
566
+ method: :array,
567
+ min: 1,
568
+ check: FieldHash
569
+ }.freeze,
570
+ }.freeze,
571
+ }.freeze
572
+
573
+
574
+ # Entry
575
+ ItemEntry = {
576
+ method: :hash,
577
+ required: {
578
+ 'icfs' => FieldIcfs,
579
+ 'caseid' => FieldCaseid,
580
+ 'entry' => Validate::IsIntPos,
581
+ 'log' => Validate::IsIntPos,
582
+ 'user' => FieldUsergrp,
583
+ 'time' => Validate::IsIntPos,
584
+ 'title' => FieldTitle,
585
+ 'content' => FieldContent,
586
+ 'tags' => {
587
+ method: :any,
588
+ check: [
589
+ SubTagsEmpty,
590
+ SubTagsEntry,
591
+ ].freeze
592
+ }.freeze,
593
+ }.freeze,
594
+ optional: {
595
+ 'index' => SubIndexes,
596
+ 'action' => Validate::IsIntPos,
597
+ 'perms' => SubPerms,
598
+ 'stats' => SubStats,
599
+ 'files' => {
600
+ method: :array,
601
+ min: 1,
602
+ check: SubFileOld
603
+ }.freeze
604
+ }.freeze
605
+ }.freeze
606
+
607
+
608
+ # Action
609
+ ItemAction = {
610
+ method: :hash,
611
+ required: {
612
+ 'icfs' => FieldIcfs,
613
+ 'caseid' => FieldCaseid,
614
+ 'action' => Validate::IsIntPos,
615
+ 'log' => Validate::IsIntPos,
616
+ 'tasks' => SubTasks
617
+ }.freeze
618
+ }.freeze
619
+
620
+
621
+ # Index
622
+ ItemIndex = {
623
+ method: :hash,
624
+ required: {
625
+ 'icfs' => FieldIcfs,
626
+ 'caseid' => FieldCaseid,
627
+ 'index' => Validate::IsIntPos,
628
+ 'log' => Validate::IsIntPos,
629
+ 'title' => FieldTitle,
630
+ 'content' => FieldContent,
631
+ 'tags' => SubTags
632
+ }.freeze,
633
+ }.freeze
634
+
635
+
636
+ # Current
637
+ ItemCurrent = {
638
+ method: :hash,
639
+ required: {
640
+ 'icfs' => FieldIcfs,
641
+ 'caseid' => FieldCaseid,
642
+ 'log' => Validate::IsIntPos,
643
+ 'hash' => FieldHash,
644
+ 'entry' => Validate::IsIntPos,
645
+ 'action' => Validate::IsIntUns,
646
+ 'index' => Validate::IsIntUns,
647
+ }.freeze
648
+ }.freeze
649
+
650
+
651
+ end # module ICFS::Items
652
+
653
+ end # module ICFS