Compare commits

...

No commits in common. "main" and "feature/db_storage" have entirely different histories.

36 changed files with 1790 additions and 2 deletions

436
.gitignore vendored Normal file
View File

@ -0,0 +1,436 @@
Skip to content
Pull requests
Issues
Codespaces
Marketplace
Explore
@danielschick
github /
gitignore
Public
Fork your own copy of github/gitignore
Code
Pull requests 371
Actions
Security
Insights
Beta Try the new code view
gitignore/VisualStudio.gitignore
@n0099
n0099 [VisualStudio.gitignore] remove a trailing space
Latest commit 491040e Jan 26, 2022
History
165 contributors
@shiftkey
@arcresu
@aroben
@bbodenmiller
@HassanHashemi
@haacked
@niik
@AArnott
@sayedihashimi
@saschanaz
@bdougie
@OsirisTerje
398 lines (319 sloc) 6.7 KB
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp

25
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "src/server/BreCal",
"FLASK_DEBUG": "1"
},
"args": [
"run" //,
// "--no-debugger",
//"--no-reload"
],
"jinja": true,
"justMyCode": true
}
]
}

View File

@ -1,2 +1,25 @@
# brecal
Bremen calling
# Bremen Calling
___
Projekt zur verbesserten Kommunikation der maritimen Partner bei Schiffsanläufen in Bremen.
## Anforderungen
## Architektur
Die Architektur besteht aus einer Datenbank und einem in Python implementierten Backend, das eine API zu Verfügung stellt. Diese API ist als OpenAPI 3.0 spezifiziert.
Die Anwendung selbst kommuniziert nur über diese API mit der Datenbank. Es sind damit unterschiedliche Anwendungsplattformen denkbar, etwa eine Web-, Mobile- oder Windows Desktop Anwendung.
In dieser [Folie](docs/Architektur.pptx) ist ein Bild / Überblick enthalten.
Ein erster Gedanke des Datenbank-Layouts sieht folgendermaßen aus:
![image](docs/datenbank.jpeg)
## Entwicklung
### Postman
Zum Debuggen der Flask App verwende ich dieses Tutorial:
https://code.visualstudio.com/docs/python/tutorial-flask#_create-a-project-environment-for-the-flask-tutorial

Binary file not shown.

BIN
docs/AMPELKONZEPT_V1.docx Normal file

Binary file not shown.

BIN
docs/Ablaufplan.pptx Normal file

Binary file not shown.

BIN
docs/Arbeitspakete.xlsx Normal file

Binary file not shown.

BIN
docs/Architektur.pptx Normal file

Binary file not shown.

BIN
docs/BremenCalling.pptx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
docs/Gesamtansicht.pptx Normal file

Binary file not shown.

BIN
docs/datenbank.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

226
misc/create_schema.sql Normal file
View File

@ -0,0 +1,226 @@
CREATE DATABASE `bremen_calling` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
CREATE TABLE `participant` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL,
`street` varchar(128) DEFAULT NULL,
`postal_code` varchar(5) DEFAULT NULL,
`city` varchar(64) DEFAULT NULL,
`flags` int(10) unsigned DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='An organization taking part';
CREATE TABLE `ship` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`imo` int(11) DEFAULT NULL,
`callsign` varchar(8) DEFAULT NULL,
`length` FLOAT NULL DEFAULT NULL,
`width` FLOAT NULL DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `shipcall` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`ship_id` INT(11) UNSIGNED NULL DEFAULT NULL,
`type` TINYINT(4) NULL DEFAULT NULL,
`eta` DATETIME NULL DEFAULT NULL,
`voyage` VARCHAR(16) NULL DEFAULT NULL,
`etd` DATETIME NULL DEFAULT NULL,
`arrival_berth_id` INT(10) UNSIGNED NULL DEFAULT NULL,
`departure_berth_id` INT(10) UNSIGNED NULL DEFAULT NULL,
`tug_required` BIT(1) NULL DEFAULT NULL,
`pilot_required` BIT(1) NULL DEFAULT NULL,
`flags` INT(10) UNSIGNED NULL DEFAULT 0,
`pier_side` BIT(1) NULL DEFAULT NULL,
`bunkering` BIT(1) NULL DEFAULT NULL,
`replenishing` BIT(1) NULL DEFAULT NULL,
`draft` FLOAT NULL DEFAULT NULL,
`tidal_window_from` DATETIME NULL DEFAULT NULL,
`tidal_window_tp` DATETIME NULL DEFAULT NULL,
`rain_sensitive_cargo` BIT(1) NULL DEFAULT b'0',
`recommended_tugs` INT(11) NULL DEFAULT 0,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_SHIPCALL_SHIP` (`ship_id`),
INDEX `FK_SHIPCALL_BERTH_ARRIVAL` (`arrival_berth_id`),
INDEX `FK_SHIPCALL_BERTH_DEPARTURE` (`departure_berth_id`),
CONSTRAINT `FK_SHIPCALL_BERTH_ARRIVAL` FOREIGN KEY (`arrival_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_BERTH_DEPARTURE` FOREIGN KEY (`departure_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_SHIP` FOREIGN KEY (`ship_id`) REFERENCES `ship` (`id`)
)
COMMENT='Incoming, outgoing or moving to another berth'
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `times` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`start_planned` datetime DEFAULT NULL,
`end_planned` datetime DEFAULT NULL,
`duration_planned` int(11) DEFAULT NULL,
`start_actual` datetime DEFAULT NULL,
`end_actual` datetime DEFAULT NULL,
`duration_actual` int(11) DEFAULT NULL,
`shipcall_id` int(11) UNSIGNED DEFAULT NULL,
`participant_id` int(11) UNSIGNED DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_TIME_SHIPCALL` (`shipcall_id`),
INDEX `FK_TIME_PART` (`participant_id`),
CONSTRAINT `FK_TIME_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_TIME_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='the planned time for the participants work';
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`participant_id` int(11) UNSIGNED DEFAULT NULL,
`first_name` varchar(45) DEFAULT NULL,
`last_name` varchar(45) DEFAULT NULL,
`user_name` varchar(45) DEFAULT NULL,
`password_hash` varchar(128) DEFAULT NULL,
`api_key` varchar(256) DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_USER_PART` (`participant_id`),
CONSTRAINT `FK_USER_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='member of a participant';
CREATE TABLE `participant` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NULL DEFAULT NULL,
`street` VARCHAR(128) NULL DEFAULT NULL,
`postal_code` VARCHAR(5) NULL DEFAULT NULL,
`city` VARCHAR(64) NULL DEFAULT NULL,
`roles` INT(10) UNSIGNED NULL DEFAULT 0 COMMENT 'Bitarray of assigned roles',
`flags` INT(10) UNSIGNED NULL DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='An organization taking part';
CREATE TABLE `berth` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(128) NULL DEFAULT NULL COMMENT 'Descriptive name',
`participant_id` INT(10) UNSIGNED NULL DEFAULT NULL COMMENT 'If berth belongs to a participant, reference it here',
`lock` BIT(1) NULL DEFAULT NULL COMMENT 'The lock must be used',
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_BERTH_PART` (`participant_id`),
CONSTRAINT `FK_BERTH_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
)
COMMENT='Berth of ship for a ship call'
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
;
CREATE TABLE `shipcall_participant_map` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`shipcall_id` int(10) unsigned DEFAULT NULL,
`participant_id` int(10) unsigned DEFAULT NULL,
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Associates a participant with a shipcall';
CREATE TABLE `shipcall_tug_map` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`shipcall_id` INT(11) UNSIGNED NOT NULL COMMENT 'Ref to ship call',
`ship_id` INT(11) UNSIGNED NOT NULL COMMENT 'Ref to ship (that is a tug)',
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_SCT_SHIP` (`ship_id`),
INDEX `FK_SCT_SHIPCALL` (`shipcall_id`),
CONSTRAINT `FK_SCT_SHIP` FOREIGN KEY (`ship_id`) REFERENCES `ship` (`id`),
CONSTRAINT `FK_SCT_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`)
)
COMMENT='Mapping table that assigns tugs to a ship call'
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
;
CREATE TABLE `notification` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`times_id` INT(11) UNSIGNED NOT NULL COMMENT 'times record that caused the notification',
`participant_id` INT(11) UNSIGNED NOT NULL COMMENT 'participant ref',
`acknowledged` BIT(1) NULL DEFAULT b'0' COMMENT 'true if UI acknowledged',
`level` TINYINT(4) NULL DEFAULT NULL COMMENT 'severity of the notification',
`type` TINYINT(4) NULL DEFAULT NULL COMMENT 'Email/UI/Other',
`message` VARCHAR(256) NULL DEFAULT NULL COMMENT 'individual message',
`created` DATETIME NULL DEFAULT current_timestamp(),
`modified` DATETIME NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
INDEX `FK_NOT_TIMES` (`times_id`),
INDEX `FK_NOT_PART` (`participant_id`),
CONSTRAINT `FK_NOT_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_NOT_TIMES` FOREIGN KEY (`times_id`) REFERENCES `times` (`id`)
)
COMMENT='An entry corresponds to an alarm given by a violated rule during times update'
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `role` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '0' COMMENT 'unique role name',
`description` VARCHAR(255) NULL DEFAULT '0' COMMENT 'role description',
PRIMARY KEY (`id`),
UNIQUE INDEX `name` (`name`)
)
COMMENT='logical group of securables for one or more user'
DEFAULT CHARSET=utf8mb4
ENGINE=InnoDB
;
CREATE TABLE `securable` (
`id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE INDEX `name` (`name`)
)
COMMENT='Actual permission on a single(!) feature or operation'
DEFAULT CHARSET=utf8mb4
ENGINE=InnoDB
;
CREATE TABLE `user_role_map` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT(10) UNSIGNED NOT NULL DEFAULT 0,
`role_id` INT(10) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `FK_USER_ROLE` (`user_id`),
INDEX `FK_ROLE_USER` (`role_id`),
CONSTRAINT `FK_ROLE_USER` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FK_USER_ROLE` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
)
COMMENT='Assigns a user to a role'
DEFAULT CHARSET=utf8mb4
ENGINE=InnoDB
;
CREATE TABLE `role_securable_map` (
`id` INT(10) UNSIGNED NOT NULL,
`role_id` INT(10) UNSIGNED NOT NULL,
`securable_id` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
INDEX `FK_ROLE_SECURABLE` (`role_id`),
INDEX `FK_SECURABLE_ROLE` (`securable_id`),
CONSTRAINT `FK_ROLE_SECURABLE` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FK_SECURABLE_ROLE` FOREIGN KEY (`securable_id`) REFERENCES `securable` (`id`)
)
COMMENT='Assigns securables to roles'
DEFAULT CHARSET=utf8mb4
ENGINE=InnoDB
;

478
misc/index.yaml Normal file
View File

@ -0,0 +1,478 @@
openapi: '3.0.0'
info:
version: '1.0.0'
title: 'Bremen calling API'
description: Administer DEBRE ship calls, times and notifications
termsOfService: "https://www.bsmd.de/" # url to terms page
contact:
name: "Bremen calling API"
url: "https://www.textbausteine.net"
email: "info@textbausteine.net"
license:
name: "Use at your own risk"
url: "https://www.bsmd.de/license"
servers:
# tutorial: https://idratherbewriting.com/learnapidoc/pubapis_openapi_step3_servers_object.html
- url : "https://puls200.dyn-dns.org:8088/brecal/api"
description: "Test server self-hosted by yours truly"
paths:
# tutorial: https://idratherbewriting.com/learnapidoc/pubapis_openapi_step4_paths_object.html
/verify:
get:
summary: Returns a session key if successful
responses:
200:
description: Successful response
content:
application/json:
schema:
title: Session key
type: string
400:
$ref: '#/components/responses/400'
403:
$ref: '#/components/responses/403'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/shipcalls:
get:
summary: Gets a list of ship calls
parameters:
- name: participant_id
in: query
required: true
description: "**Id of participant**. *Example: 2*. Id of participant entity requesting ship calls"
schema:
type: integer
responses:
200:
description: ship call list
content:
application/json:
schema:
$ref: '#/components/schemas/shipcalls'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
post:
summary: Create a new ship call
requestBody:
description: Creates a new ship call. **Do not** provide id parameter.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/shipcall'
responses:
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
put:
summary: Updates a ship call
requestBody:
description: Creates a new ship call. The id parameter is **required**.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/shipcall'
responses:
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/ships:
get:
summary: gets a list of registered shipcalls
responses:
200:
description: list of ships
content:
application/json:
schema:
$ref: '#/components/schemas/ship_list'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/participant:
get:
summary: gets a particular participant entry corresponding to user id
parameters:
- name: user_id
in: query
required: true
description: "**Id of user**. *Example: 2*. User id returned by verify call."
schema:
type: integer
responses:
200:
description: ship call list
content:
application/json:
schema:
$ref: '#/components/schemas/participant'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/times:
get:
summary: Get all recorded times for a a ship call
parameters:
- name: shipcall_id
in: query
description: "**Id**. *Example: 42*. Id of referenced ship call."
schema:
type: integer
responses:
200:
description: list of recorded times
content:
application/json:
schema:
$ref: '#/components/schemas/times_list'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
post:
summary: Create a new times entry for a ship call
requestBody:
description: Times entry that will be added to the ship call. **Do not** provide id parameter.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/times'
responses:
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
put:
summary: Update a times entry for a ship call
requestBody:
description: Times entry that will be added to the ship call. The id parameter is **required**.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/times'
responses:
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
delete:
summary: Delete a times entry for a ship call.
parameters:
- name: id
in: query
required: true
schema:
$ref: '#/components/schemas/timesId'
responses:
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/notifications:
get:
summary: Gets a list of notifications pursuant to a specified participant and ship call
parameters:
- name: participant_id
in: query
required: true
description: "**Id of participant**. *Example: 2*. Id returned through loading of participant"
schema:
type: integer
- name: shipcall_id
in: query
required: true
description: "**Id of ship call**. *Example: 52*. Id given in ship call list"
schema:
$ref: '#/components/schemas/shipcallId'
responses:
200:
description: notification list
content:
application/json:
schema:
$ref: '#/components/schemas/notification'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
/berths:
get:
summary: Gets a list of all berths registered
responses:
200:
description: list of berths
content:
application/json:
schema:
$ref: '#/components/schemas/berth_list'
400:
$ref: '#/components/responses/400'
401:
$ref: '#/components/responses/401'
500:
$ref: '#/components/responses/500'
503:
$ref: '#/components/responses/503'
components:
schemas:
timesId:
description: The unique identifier for a times entry
type: integer
shipcallId:
description: The unique identifier of a ship call
type: integer
shipcall:
type: object
required:
- id
- ship_id
- type
- eta
- voyage
- etd
properties:
id:
$ref: '#/components/schemas/shipcallId'
type:
type: string
enum:
- incoming
- outgoing
- shifting
description:
type: string
shipcalls:
type: array
items:
$ref: '#/components/schemas/shipcall'
times:
type: object
description: the id parameter needs to be missing on POST and to be present on PUT (Update) calls, otherwise a 400 response will be generated
required:
- shipcall_id
- participant_id
properties:
id:
type: integer
start_planned:
type: string
format: date-time
end_planned:
type: string
format: date-time
duration_planned:
type: integer
start_actual:
type: string
format: date-time
end_actual:
type: string
format: date-time
shipcall_id:
type: integer
participant_id:
type: integer
times_list:
type: array
items:
$ref: '#/components/schemas/times'
berth:
type: object
description: Ship berth used for a ship call
properties:
id:
type: integer
name1:
type: string
name2:
type: string
berth_list:
type: array
items:
$ref: '#/components/schemas/berth'
ship:
type: object
description: a ship
properties:
id:
type: integer
name:
type: string
imo:
type: integer
callsign:
type: string
length:
type: number
format: float
width:
type: number
format: float
created:
type: string
format: date-time
modified:
type: string
format: date-time
ship_list:
type: array
items:
$ref: '#/components/schemas/ship'
notification:
type: object
description: a notification created by the engine if a times entry violates a rule
properties:
id:
type: integer
times_id:
type: integer
participant_id:
type: integer
notification_type:
type: string
enum: [undefined, email, push]
timestamp:
type: string
format: date-time
acknowledged:
type: boolean
notification_list:
type: array
items:
$ref: '#/components/schemas/notification'
participant:
type: object
description: A organisational entity that participates in Bremen Calling
properties:
id:
type: integer
name:
type: string
street:
type: string
postal code:
type: string
city:
type: string
Error:
type: object
required:
- message
properties:
message:
description: A human readable error message
type: string
securitySchemes:
ApiKey:
type: apiKey
in: header
name: X-Api-Key
responses:
400:
description: Invalid input
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
401:
description: Not authorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
500:
description: Unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
503:
description: Not available
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- ApiKey: []
externalDocs:
url: http://textbausteine.net/
description: Extra documentation and conditions for Bremen Calling

View File

@ -0,0 +1,49 @@
from flask import Flask
import os
import logging
import secrets
from . import local_db
from .api import shipcalls
from .api import verify
from .api import participant
from .api import times
from .api import notifications
from .api import berths
sessions = dict()
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev'
)
if test_config is None:
app.config.from_pyfile('config.py', silent=True)
else:
app.config.from_mapping(test_config)
try:
print(f'Instance path = {app.instance_path}')
os.makedirs(app.instance_path)
except OSError:
pass
# Add blueprints
app.register_blueprint(shipcalls.bp)
app.register_blueprint(verify.bp)
app.register_blueprint(participant.bp)
app.register_blueprint(times.bp)
app.register_blueprint(notifications.bp)
app.register_blueprint(berths.bp)
logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool()
logging.info('App started')
return app
def create_api_key():
return secrets.token_urlsafe(16)

View File

@ -0,0 +1,13 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('berths', __name__)
@bp.route('/berths', methods=['get'])
def GetBerths():
token = request.headers.get('Authentication')
return impl.berths.GetBerths(token)

View File

@ -0,0 +1,17 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('notifications', __name__)
@bp.route('/notifications', methods=['get'])
def GetNotifications():
options = {}
options["participant_id"] = request.args.get("participant_id")
options["shipcall_id"] = request.args.get("shipcall_id")
return impl.notifications.GetNotifications(options)

View File

@ -0,0 +1,16 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('participant', __name__)
@bp.route('/participant', methods=['get'])
def GetParticipant():
options = {}
options["user_id"] = request.args.get("user_id")
return impl.participant.GetParticipant(options)

View File

@ -0,0 +1,36 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('shipcalls', __name__)
@bp.route('/shipcalls', methods=['get'])
def GetShipcalls():
options = {}
options["participant_id"] = request.args.get("participant_id")
return impl.shipcalls.GetShipcalls(options)
@bp.route('/shipcalls', methods=['post'])
def PostShipcalls():
schema = model.Shipcall()
body = parser.parse(schema, request, location='json')
return impl.shipcalls.PostShipcalls(body)
@bp.route('/shipcalls', methods=['put'])
def PutShipcalls():
schema = model.Shipcall()
body = parser.parse(schema, request, location='json')
return impl.shipcalls.PutShipcalls(body)

View File

@ -0,0 +1,45 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('times', __name__)
@bp.route('/times', methods=['get'])
def GetTimes():
options = {}
options["shipcall_id"] = request.args.get("shipcall_id")
return impl.times.GetTimes(options)
@bp.route('/times', methods=['post'])
def PostTimes():
schema = model.Times()
body = parser.parse(schema, request, location='json')
return impl.times.PostTimes(body)
@bp.route('/times', methods=['put'])
def PutTimes():
schema = model.Times()
body = parser.parse(schema, request, location='json')
return impl.times.PutTimes(body)
@bp.route('/times', methods=['delete'])
def DeleteTimes():
options = {}
options["id"] = request.args.get("id")
return impl.times.DeleteTimes(options)

View File

@ -0,0 +1,13 @@
from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl
bp = Blueprint('verify', __name__)
@bp.route('/verify', methods=['get'])
def GetVerify():
apikey = request.headers.get('X-Api-Key')
return impl.verify.GetVerify(apikey)

View File

@ -0,0 +1,6 @@
from . import berths
from . import notifications
from . import participant
from . import shipcalls
from . import times
from . import verify

View File

@ -0,0 +1,33 @@
import json
import logging
import pydapper
from ..schemas import model
from .. import local_db
def GetBerths(token):
"""
No parameters, gets all entries
"""
# TODO: validate token
try:
commands = pydapper.using(local_db.connection_pool)
data = commands.query("SELECT id, name1, name2 FROM berth ORDER BY name1", model=model.Berth)
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("call failed"), 500
print(data)
return json.dumps(data, default=model.obj_dict), 200

View File

@ -0,0 +1,25 @@
import json
import logging
def GetNotifications(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["participant_id"]: **Id of participant**. *Example: 2*. Id returned through loading of participant
options["shipcall_id"]: **Id of ship call**. *Example: 52*. Id given in ship call list
"""
# Implement your business logic here
# All the parameters are present in the options argument
return json.dumps({
"acknowledged": "<boolean>",
"id": "<integer>",
"notification_type": "<string>",
"participant_id": "<integer>",
"times_id": "<integer>",
"timestamp": "<date-time>",
}), 200

View File

@ -0,0 +1,41 @@
import sys
import json
import mariadb
import logging
def GetParticipant(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["user_id"]: **Id of user**. *Example: 2*. User id returned by verify call.
"""
try:
conn = mariadb.connect(host="lager", user="ds", password="Gurkensalat48", database="bremen_calling")
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")
sys.exit(1)
cur = conn.cursor()
id = options["user_id"]
query = "SELECT p.id, p.name, p.street, p.postal_code, p.city, p.flags FROM participant p INNER JOIN user u WHERE u.participant_id = p.id AND u.id = %d "
data = [id]
cur.execute(query, data)
for (id, name, street, postal_code, city, flags) in cur:
# Implement your business logic here
# All the parameters are present in the options argument
return json.dumps({
"id": id,
"city": city,
"name": name,
"postal code": postal_code,
"street": street,
}), 200
return json.dumps([]), 200

View File

@ -0,0 +1,45 @@
import json
import logging
def GetShipcalls(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["participant_id"]: **Id of participant**. *Example: 2*. Id of participant entity requesting ship calls
"""
# Implement your business logic here
# All the parameters are present in the options argument
return json.dumps([{
"description": "<string>",
"id": "<ShipcallId>",
"type": "<string>",
}]), 200
def PostShipcalls(body):
"""
:param body: The parsed body of the request
"""
# Implement your business logic here
# All the parameters are present in the options argument
return 400
def PutShipcalls(body):
"""
:param body: The parsed body of the request
"""
# Implement your business logic here
# All the parameters are present in the options argument
return 400

View File

@ -0,0 +1,64 @@
import json
import logging
def GetTimes(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["shipcall_id"]: **Id**. *Example: 42*. Id of referenced ship call.
"""
# Implement your business logic here
# All the parameters are present in the options argument
return json.dumps([{
"duration_planned": "<integer>",
"end_actual": "<date-time>",
"end_planned": "<date-time>",
"id": "<integer>",
"participant_id": "<integer>",
"shipcall_id": "<integer>",
"start_actual": "<date-time>",
"start_planned": "<date-time>",
"remark":"<string>"
}]), 200
def PostTimes(body):
"""
:param body: The parsed body of the request
"""
# Implement your business logic here
# All the parameters are present in the options argument
return 400
def PutTimes(body):
"""
:param body: The parsed body of the request
"""
# Implement your business logic here
# All the parameters are present in the options argument
return 400
def DeleteTimes(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["id"]
"""
# Implement your business logic here
# All the parameters are present in the options argument
return 400

View File

@ -0,0 +1,33 @@
import json
import logging
import pydapper
from ..schemas import model
from ..schemas import __init__
def GetVerify(apikey):
"""
:param apikey: the api-key registered with the user
"""
if not apikey:
return json.dumps("missing api key"), 400
sentinel = object()
try:
commands = pydapper.using(__init__.connection_pool)
data = commands.query_single_or_default("SELECT id from `user` WHERE api_key=?api_key?", default=sentinel, model=model.User, param={"api_key" : apikey})
if(data is sentinel):
return json.dumps("wrong api key", 403)
except Exception as ex:
logging.error(ex)
return json.dumps("logon failed"), 500
# TODO: user authenticated: Create,store and transmit JWT token
return json.dumps("<integer>"), 200

View File

@ -0,0 +1,26 @@
import mysql.connector
import pydapper
import logging
connection_pool = None
def initPool():
try:
global connection_pool
connection_pool = mysql.connector.connect(
host="lager",
port=3306,
user="ds",
password="HalloWach23",
pool_name="brecal_pool",
pool_size=20,
database="bremen_calling"
)
commands = pydapper.using(connection_pool)
data = commands.query_single("SELECT id from `user`")
print("DB connection successful")
except mysql.connector.PoolError as e:
logging.error(f"Failed to create connection pool: {e}")
print(e)

View File

@ -0,0 +1,95 @@
from marshmallow import Schema, fields
from dataclasses import dataclass
import json
def obj_dict(obj):
return obj.__dict__
@dataclass
class Berth(Schema):
id: int
name1: str
name2: str
id = fields.Int()
name1 = fields.String()
name2 = fields.String()
class Error(Schema):
message = fields.String(required=True,)
class GetVerifyInlineResp(Schema):
pass
class Notification(Schema):
acknowledged = fields.Boolean()
id = fields.Int()
notification_type = fields.String()
participant_id = fields.Int()
times_id = fields.Int()
timestamp = fields.DateTime()
class Participant(Schema):
city = fields.String()
id = fields.Int()
name = fields.String()
postal_code = fields.String(data_key="postal code",)
street = fields.String()
class Shipcall(Schema):
description = fields.String()
id = fields.Raw(required=True,)
type = fields.String(required=True,)
class ShipcallId(Schema):
pass
class Times(Schema):
duration_planned = fields.Int()
end_actual = fields.DateTime()
end_planned = fields.DateTime()
id = fields.Int()
participant_id = fields.Int(required=True,)
shipcall_id = fields.Int(required=True,)
start_actual = fields.DateTime()
start_planned = fields.DateTime()
class TimesId(Schema):
pass
@dataclass
class BerthList(Berth):
pass
class NotificationList(Notification):
pass
class Shipcalls(Shipcall):
pass
class TimesList(Times):
pass
class User(Schema):
id = fields.Int()
participant_id = fields.Int()
first_name = fields.String()
last_name = fields.String()
user_name = fields.String()
password_hash = fields.String()
api_key = fields.String()

22
src/server/README.md Normal file
View File

@ -0,0 +1,22 @@
# BreCal
## OpenAPI 3.0 Generated Flask project
The code was originally created by exporting the API from Postman. Selecting the API on the left (root element) the code generation icon becomes visible on the right.
![image](../../docs/export_flask_project.png)
If the API gets updated I think it makes most sense to export again into a API helper branch and then merge this branch back into the project. Please do not forget to update the [index.yaml](../../misc/index.yaml) file.
The branch is named yaml_export.
All the routes are defined in 'project/api' folder.
Each route parses the request and calls the corresponding function in the 'project/impl' directory passing all the parameters and request body as function arguments.
To run this project:
```bash
pip install -r requirements.txt
export FLASK_APP='BreCal'
export FLASK_ENV=development
flask run
```

View File

@ -0,0 +1,9 @@
click==7.1.2
Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
marshmallow==3.9.1
webargs==6.1.1
Werkzeug==1.0.1
pydapper[mysql-connector-python]

12
src/server/setup.py Normal file
View File

@ -0,0 +1,12 @@
from setuptools import find_packages, setup
setup(
name='BreCal',
version='1.0.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask'
]
)