Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4dece19
update cruise_create script to take advantage of 2.11
webbpinner Aug 13, 2025
751dcae
Adds event option visibility control
webbpinner Feb 24, 2026
d9e5581
implementing changes for issue #64
webbpinner Nov 8, 2025
887a8b1
initial import to implement issue #66
webbpinner Nov 27, 2025
39e661f
Updates API key route paths
webbpinner Feb 24, 2026
1568f68
Make AuxDataRecordBuilder base class and inherit from it in the Influ…
ljones37oet Nov 25, 2025
3c6f6ed
Moved common aux data inserter code into a separate file
ljones37oet Mar 5, 2026
90e2dbe
slight loop optimization
ljones37oet Dec 2, 2025
ba63651
Don't fail if inserter config is missing query-related properties--no…
ljones37oet Dec 3, 2025
04bec35
Add functionality for opening/closing connections with AuxDataRecordB…
ljones37oet Feb 18, 2026
3459ef3
Make the set of event types to exclude individual to inserters
ljones37oet Feb 18, 2026
bd01194
Rename "aux data inserter"s to "aux data manager"s
ljones37oet Feb 18, 2026
759fed0
Move aux_data deletion/clean up to the aux_data_managers
ljones37oet Feb 20, 2026
1a3734d
Update framegrab aux data to use new framework as an example. Now, wh…
ljones37oet Mar 4, 2026
dcb011e
Add OET's old InfluxDB aux data manager as another example
ljones37oet Mar 5, 2026
88b7e97
Clean up
ljones37oet Mar 5, 2026
116efc0
Merge pull request #67 from ljones37oet/aux-data-manager
webbpinner Mar 6, 2026
c361480
bugfix: add cleaner data_source public property to avoid crash when t…
ljones37oet Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion demo/FKt230303_S0492_eventTemplates.json
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,15 @@
"Deployed"
],
"event_option_required": true,
"event_option_allow_freeform": false
"event_option_allow_freeform": false,
"event_option_visibility": {
"show_hide": "show if",
"event_option_name": "Type",
"event_option_values": [
"Push Core",
"Water Sample"
]
}
},
{
"event_option_name": "Sample Description",
Expand Down
7 changes: 7 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ const hashedPassword = (password) => {

};

const hashedApiKey = (apiKey) => {

return hashSync( apiKey, saltRounds );

};

const mvFilesToDir = (sourcePath, destPath, createIfMissing = false) => {

try {
Expand Down Expand Up @@ -484,6 +490,7 @@ module.exports = {
filePreProcessor,
flattenEventObjs,
hashedPassword,
hashedApiKey,
mvFilesToDir,
randomAsciiString,
randomString,
Expand Down
69 changes: 62 additions & 7 deletions lib/validations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Joi = require('joi');

const {
roles,
useAccessControl
} = require('../config/server_settings');

Expand Down Expand Up @@ -360,6 +361,12 @@ const eventTemplateQuery = Joi.object({
sort: Joi.string().valid('event_name').optional()
}).optional().label('eventTemplateQuery');

const eventTemplateVisibility = Joi.object({
show_hide: Joi.string().required(),
event_option_name: Joi.string().required(),
event_option_values: Joi.array().items(Joi.string()).required()
});

const eventTemplateResponse = Joi.object({
id: Joi.object(),
event_name: Joi.string(),
Expand All @@ -376,7 +383,8 @@ const eventTemplateResponse = Joi.object({
event_option_default_value: Joi.string().allow(''),
event_option_values: Joi.array().items(Joi.string()),
event_option_allow_freeform: Joi.boolean(),
event_option_required: Joi.boolean()
event_option_required: Joi.boolean(),
event_option_visibility: eventTemplateVisibility.optional()
}))
}).label('eventTemplateResponse');

Expand All @@ -396,7 +404,8 @@ const eventTemplateCreatePayload = Joi.object({
event_option_default_value: Joi.string().allow('').optional(),
event_option_values: Joi.array().items(Joi.string()).required(),
event_option_allow_freeform: Joi.boolean().required(),
event_option_required: Joi.boolean().required()
event_option_required: Joi.boolean().required(),
event_option_visibility: eventTemplateVisibility.optional()
})).optional()
}).label('eventTemplateCreatePayload');

Expand All @@ -415,7 +424,8 @@ const eventTemplateUpdatePayload = Joi.object({
event_option_default_value: Joi.string().allow('').optional(),
event_option_values: Joi.array().items(Joi.string()).required(),
event_option_allow_freeform: Joi.boolean().required(),
event_option_required: Joi.boolean().required()
event_option_required: Joi.boolean().required(),
event_option_visibility: eventTemplateVisibility.optional()
})).optional()
}).required().min(1).label('eventTemplateUpdatePayload');

Expand Down Expand Up @@ -626,7 +636,7 @@ const userCreatePayload = Joi.object({
fullname: Joi.string().min(1).max(100).required(),
email: Joi.string().email().required(),
password: Joi.string().allow('').max(50).required(),
roles: Joi.array().items(Joi.string()).min(1).required(),
roles: Joi.array().items(Joi.string().valid(...roles)).min(1).required(),
system_user: Joi.boolean().optional(),
disabled: Joi.boolean().optional(),
resetURL: Joi.string().uri().required()
Expand All @@ -637,7 +647,7 @@ const userUpdatePayload = Joi.object({
fullname: Joi.string().min(1).max(100).optional(),
// email: Joi.string().email().optional(),
password: Joi.string().allow('').max(50).optional(),
roles: Joi.array().items(Joi.string()).min(1).optional(),
roles: Joi.array().items(Joi.string().valid(...roles)).min(1).optional(),
system_user: Joi.boolean().optional(),
disabled: Joi.boolean().optional()
}).required().min(1).label('userUpdatePayload');
Expand All @@ -649,7 +659,7 @@ const userResponse = Joi.object({
last_login: Joi.date(),
username: Joi.string(),
fullname: Joi.string(),
roles: Joi.array().items(Joi.string()),
roles: Joi.array().items(Joi.string().valid(...roles)),
disabled: Joi.boolean()
}).label('userResponse');

Expand All @@ -658,6 +668,46 @@ const userSuccessResponse = Joi.alternatives().try(
Joi.array().items(userResponse)
).label('userSuccessResponse');


// api keys
// ----------------------------------------------------------------------------
const apiKeyParam = Joi.object({
id: Joi.string().length(24).required()
}).label('apiKeyParam');

const apiKeyQuery = Joi.object({
id: Joi.string().length(24).optional(),
label: Joi.string().max(100).optional()
}).label('apiKeyQuery');

const apiKeyCreatePayload = Joi.object({
label: Joi.string().max(100).required(),
scope: Joi.array().items(Joi.string()).optional(),
expires: Joi.date().optional()
}).label('apiKeyCreatePayload');

const apiKeyResponse = Joi.object({
id: Joi.object(),
user_id: Joi.object(),
label: Joi.string(),
scope: Joi.array().items(Joi.string()),
created: Joi.date(),
last_used: Joi.date().allow(null),
disabled: Joi.boolean(),
expires: Joi.date().allow(null)
}).label('apiKeyResponse');

const apiKeySuccessResponse = Joi.alternatives().try(
apiKeyResponse,
Joi.array().items(apiKeyResponse)
).label('apiKeySuccessResponse');

const apiKeyUpdatePayload = Joi.object({
label: Joi.string().max(100).optional(),
scope: Joi.array().items(Joi.string()).optional(),
expires: Joi.date().optional()
}).label('apiKeyUpdatePayload');

module.exports = {
authorizationHeader,
autoLoginPayload,
Expand Down Expand Up @@ -733,5 +783,10 @@ module.exports = {
userQuery,
userSuccessResponse,
userToken,
userUpdatePayload
userUpdatePayload,
apiKeyCreatePayload,
apiKeyParam,
apiKeyQuery,
apiKeySuccessResponse,
apiKeyUpdatePayload
};
99 changes: 99 additions & 0 deletions misc/aux_data_file_cleaners/base_aux_data_file_cleaner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
'''
FILE: base_aux_data_file_cleaner.py

DESCRIPTION: Base class for cleaning up any changes made by aux_data_inserters outside
of the aux data mongo collection.

BUGS:
NOTES:
AUTHOR: Lindsey Jones
COMPANY: OET
VERSION: 1.0
CREATED: 2026-02-20
REVISION:

LICENSE INFO: This code is licensed under MIT license (see LICENSE.txt for details)
Copyright (C) OceanDataTools.org 2025
'''
import logging

from abc import ABC, abstractmethod


class AuxDataFileCleaner(ABC):
'''
Abstract base class for building sealog aux_data records from various data sources.
Handles common functionality for querying external data sources and building
aux_data records with transformations.
'''

def __init__(self, aux_data_config):
'''
Initialize the base builder with configuration.
'''
self._data_source = aux_data_config['data_source']
self.logger = logging.getLogger(__name__)

def _get_aux_data_for_source(self, event):
'''
Get the aux data record for the given event based on the data source.

Args:
event (dict): Event dictionary containing 'id' and 'ts' keys

Returns:
str or None: ID of cleaned aux data record, if there is one
'''
all_aux_data = event.get('aux_data', {})
if not all_aux_data:
self.logger.debug("No aux data found for event %s", event['id'])
return None

# Find the first aux_data record that matches the data source
# There should only ever be one record per data source
aux_data_for_source = next(
(aux_data for aux_data in all_aux_data
if aux_data["data_source"] == self._data_source),
None # default if not found
)

if not aux_data_for_source:
self.logger.debug("No %s aux data found for event %s", self._data_source, event['id'])

return aux_data_for_source

@abstractmethod
def open_connections(self):
'''
Open any necessary connections to external data sources.
Must be implemented by subclasses.
'''

@abstractmethod
def close_connections(self):
'''
Close any open connections to external data sources.
Must be implemented by subclasses.
'''

@abstractmethod
def clean_aux_data_record(self, event, dry_run):
'''
Do any clean up required for the given event
Must be implemented by subclasses.

Args:
event (dict): Event dictionary containing 'id' and 'ts' keys
dry_run (bool): If True, do not actually delete any aux data records

Returns:
dict or None: Aux data record or None if no data available
'''

@property
def data_source(self):
'''
Getter method for the data_source property
'''
return self._data_source
76 changes: 76 additions & 0 deletions misc/aux_data_file_cleaners/delete_files_aux_data_file_cleaner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
'''
FILE: delete_files_aux_data_file_cleaner.py

DESCRIPTION: Cleans up the files created by the aux data creation process.

BUGS:
NOTES:
AUTHOR: Lindsey Jones
COMPANY: OET
VERSION: 1.0
CREATED: 2026-02-20
REVISION:

LICENSE INFO: This code is licensed under MIT license (see LICENSE.txt for details)
Copyright (C) OceanDataTools.org 2025
'''
import os
import sys

from os.path import dirname, realpath
sys.path.append(dirname(dirname(dirname(realpath(__file__)))))

from misc.aux_data_file_cleaners.base_aux_data_file_cleaner import AuxDataFileCleaner


class DeleteFilesAuxDataFileCleaner(AuxDataFileCleaner):
'''
Cleans up the files created by the stillcap_ffmpeg aux data creation process
'''

def open_connections(self):
'''
No connections to open.
'''

def close_connections(self):
'''
No connections to close.
'''

def clean_aux_data_record(self, event, dry_run):
'''
Do any clean up required for the given event

Args:
event (dict): Event dictionary containing 'id' and 'ts' keys
dry_run (bool): If True, do not actually delete any aux data records

Returns:
str or None: ID of cleaned aux data record, if there is one
'''
aux_data = self._get_aux_data_for_source(event)

if not aux_data:
return None

file_paths = [
data["data_value"]
for data in aux_data["data_array"]
if data["data_name"] == "filename"
]
for file_path in file_paths:
try:
self.logger.debug("Deleting file %s for event %s", file_path, event['id'])
if not dry_run:
os.remove(file_path)
except Exception as e: # pylint: disable=W0718
self.logger.error(
"Error deleting file %s for event %s: %s",
file_path,
event['id'],
e
)

return aux_data["_id"]
Loading
Loading