Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0c0bb6e
Improve routing and add submission db test
dylanpivo Apr 1, 2026
5d5c91e
Cherry pick fix
dylanpivo Mar 16, 2026
a1cd9d2
Fix conflicts
dylanpivo Apr 10, 2026
f284a17
Add doi to submission
dylanpivo Feb 27, 2026
fb5c313
loggin improvements and data submission schema translate additions
dylanpivo Feb 20, 2026
778c3dc
submission record creation
dylanpivo Feb 16, 2026
081e193
Resolve conflics
dylanpivo Feb 11, 2026
da02858
submission endpoints and nextcloud integration
dylanpivo Feb 5, 2026
5cac07b
Resolve conflict changes
dylanpivo Apr 10, 2026
d92fd91
Fix import naming bug
dylanpivo Apr 13, 2026
ea470e3
Add scopes to migrate Web UI client
dylanpivo Apr 13, 2026
529f191
Test added for datra submission schema
dylanpivo Jun 1, 2026
ed929d4
Added tests for submission router
dylanpivo Jun 2, 2026
fa9dbc5
Added alembic version for submission generation
dylanpivo Jun 8, 2026
51fc0a5
Updated slembic submission version
dylanpivo Jun 8, 2026
fb4a2cc
Remove enum creating from migration script
dylanpivo Jun 8, 2026
c0e0ac4
Added dataset url to submission model
dylanpivo Jun 23, 2026
3a235c1
Update nextcloud to stream
dylanpivo Jul 1, 2026
4af8ac2
Improve nextcloud uploading
dylanpivo Jul 3, 2026
4d4d967
Updated alembic revision and submission db test
dylanpivo Aug 26, 2026
820348f
review fixes
dylanpivo Aug 27, 2026
9e431cd
Merge pull request #65 from SAEON/metadata_submission
dylanpivo Aug 28, 2026
5dc8776
added logic for empty data on submission list item
dylanpivo Sep 21, 2026
2d50b58
Merge pull request #67 from SAEON/fix/submission_list_empty_item
dylanpivo Sep 21, 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
3 changes: 3 additions & 0 deletions migrate/systemdata/clients.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ ODP.Web.UI:
scopes:
- odp.record:read
- odp.token:read
- odp.submission:read
- odp.submission:write
- odp.submission:delete
- openid
- offline_access
grant_types:
Expand Down
4 changes: 4 additions & 0 deletions migrate/systemdata/schemas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ SAEON.ISO19115:
type: metadata
uri: https://odp.saeon.ac.za/schema/metadata/saeon/iso19115

SAEON.DataSubmission:
type: metadata
uri: https://odp.saeon.ac.za/schema/metadata/saeon/data-submission

SAEON.EML:
type: metadata
uri: https://odp.saeon.ac.za/schema/metadata/saeon/eml
Expand Down
49 changes: 49 additions & 0 deletions migrate/versions/2026_06_08_8a386c42418d_add_submission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""add_submission

Revision ID: 8a386c42418d
Revises: b7dd3a950ed5
Create Date: 2026-06-08 09:42:45.759309

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '8a386c42418d'
down_revision = 'b7dd3a950ed5'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Create the submissionstatus enum type
submission_status = postgresql.ENUM('in_progress', 'submitted', 'accepted', name='submissionstatus')

op.create_table('submission',
sa.Column('id', sa.Integer(), sa.Identity(), nullable=False),
sa.Column('doi', sa.String(), nullable=True),
sa.Column('user_id', sa.String(), nullable=False),
sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column('status', submission_status, nullable=False),
sa.Column('dataset_file_name', sa.String(), nullable=True),
sa.Column('dataset_url', sa.String(), nullable=True),
sa.Column('timestamp', sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column('collection_id', sa.String(), nullable=True),
sa.Column('schema_id', sa.String(), nullable=True),
sa.Column('record_id', sa.String(), nullable=True),
sa.ForeignKeyConstraint(['collection_id'], ['collection.id'], name=op.f('submission_collection_id_fkey')),
sa.ForeignKeyConstraint(['record_id'], ['record.id'], name=op.f('submission_record_id_fkey')),
sa.PrimaryKeyConstraint('id', name=op.f('submission_pkey')),
sa.UniqueConstraint('doi', name=op.f('submission_doi_key'))
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('submission')
submission_status = postgresql.ENUM('in_progress', 'submitted', 'accepted', name='submissionstatus')
submission_status.drop(op.get_bind(), checkfirst=True)
# ### end Alembic commands ###
65 changes: 65 additions & 0 deletions odp/api/lib/nextcloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import logging
import os

import requests

from odp.config import config

logger = logging.getLogger(__name__)

NEXTCLOUD_FOLDER = "/UPLOAD/ODP Data Submissions/"


def upload_file_to_nextcloud(local_path_to_file, folder_name, file_name):
if not os.path.exists(local_path_to_file):
logger.error(f"Error: Local file not found at '{local_path_to_file}'")
return False

_clear_and_create_folder(folder_name)

webdav_url = f"{config.NEXTCLOUD.URL}{NEXTCLOUD_FOLDER}{folder_name}/{file_name}"

try:
with open(local_path_to_file, 'rb') as f:
response = requests.put(
webdav_url,
data=f,
auth=(config.NEXTCLOUD.USER, config.NEXTCLOUD.PASSWORD),
verify=True
)

if response.status_code == 201 or response.status_code == 204:
logger.info(f"Success! File uploaded. Status code: {response.status_code}")
return True
else:
logger.error(f"Error during upload. Status code: {response.status_code}. Response body: {response.text}")

except requests.exceptions.RequestException as e:
logger.exception(f"Error during upload: {e}")

return False


def delete_folder_from_nextcloud(folder_name):
"""Deletes the folder from nextcloud including its contents"""
folder_url = f"{config.NEXTCLOUD.URL}{NEXTCLOUD_FOLDER}{folder_name}/"
auth = (config.NEXTCLOUD.USER, config.NEXTCLOUD.PASSWORD)

try:
requests.delete(folder_url, auth=auth, verify=True)
except requests.exceptions.RequestException as e:
logger.error(f"Error deleting folder: {e}")


def _clear_and_create_folder(folder_name):
"""Deletes the folder and then re-creates it."""
folder_url = f"{config.NEXTCLOUD.URL}{NEXTCLOUD_FOLDER}{folder_name}/"

delete_folder_from_nextcloud(folder_name)

requests.request(
"MKCOL",
folder_url,
auth=(config.NEXTCLOUD.USER, config.NEXTCLOUD.PASSWORD),
verify=True
)
Loading
Loading