Compare commits

..

24 Commits

Author SHA1 Message Date
f24880c09a fix: raise user password char length (#18)
All checks were successful
Create Release / release (push) Successful in 28s
Create Release / docker (push) Successful in 8m55s
Co-authored-by: Lunaresk <lingsonb@gmail.com>
Reviewed-on: #18
2025-06-03 21:39:00 +02:00
3622cbca05 fix: workflow login
All checks were successful
Create Release / release (push) Successful in 21s
Create Release / docker (push) Successful in 8m43s
2025-04-09 01:29:54 +02:00
637a5f7ad5 fix: stylesheet
Some checks failed
Create Release / release (push) Successful in 2m1s
Create Release / docker (push) Failing after 8m22s
2025-04-09 00:50:15 +02:00
15ba3c060a Workflow Action for Docker Repo (#2)
Reviewed-on: #2
2024-11-03 17:57:37 +00:00
bde9a50767 fix: update dockerfile 2024-11-03 15:47:29 +01:00
60e221146a Actions Workflow Fix 2024-11-03 10:51:25 +01:00
d34f2adb2f fix: correct OCR language 2024-11-02 23:46:02 +01:00
a6f229e7fb minor: set folder for receipts 2024-08-26 19:43:47 +02:00
5a453a140e major: integrate ocr to pdf reader 2024-08-25 22:19:53 +02:00
0f6f76a66b fix: money can now be entered in decimal 2024-08-18 18:39:56 +02:00
14b7e1f84c bug: model fix 2024-06-16 17:25:43 +02:00
560016cc18 minor: add table as save for unknown items 2024-06-16 16:21:57 +02:00
b57dbba5d6 minor: display payments on overview
Payments are now displayed at the bottom of the lists.
Also, receipts can now be uploaded without necessarily
providing an image of the original.
2024-02-18 17:37:06 +01:00
7ae57ae3a4 major: add calculation for payments 2024-02-09 23:12:23 +01:00
04a13635dc minor: add button for upload receipt 2023-11-27 22:13:50 +01:00
5c46a24338 bugfix 2023-11-27 00:49:21 +01:00
f41fdd076b major: add dynamic receipt item forms
New:
- Added the possibility to insert custom items on receipt upload in case
the receipt couldn't be read.
Changes:
- Changed the display of the sum at the overview. You now see what you
owe your establishment instead of just seeing how much you bought.
- In addition the sum in the overview now accounts for membership time.
You don't have to pay for something bought after you moved out for
example.
2023-11-26 23:16:05 +01:00
4d1e7eb944 major: edit database and finish check_items
New:
- Removed the connection between tables 'receipt_item' and 'item'.
Instead the position on the receipt will be stored as a primary key
with the receipt.id.
- Uploading a receipt and checking items for invoice is now working.
- Uploading a receipt now redirects directly to the form where the
items on the receipt can be marked for invoice.
- Added pagination for candidates-page.

Refactoring:
- Added _rencer_field_errors.html for centralized error display.
- Added edeka_parser.py for better management of different parsers.

Known Bugs:
- Uploading a receipt without any fields results in an error.

Todo:
- Generate a general form if no fields are recognized on the uploaded
receipt.
- Create form where Admin can confirm receipt invoices.
- Display Notification for admin when users upload receipt.
- Display Notification for admin on new candidate.
2023-11-12 16:05:09 +01:00
6c0fa5a58b major: add receipt upload
You can now upload receipts and check which ones should be accounted.
Currently without function, needs to be implemented.
2023-08-06 22:25:08 +02:00
5e4a59e15f Merge remote-tracking branch 'origin/feat_receipt_upload' 2023-07-16 23:53:30 +02:00
d311b6f8cf minor: Flatastic module started 2023-07-01 13:53:42 +02:00
8e17a2fb25 minor: function for testing purposes
Reinserted the testfunction
2023-06-20 00:01:30 +02:00
dceda1446f major: refactoring
Refactored most imports to relative paths and made some experimental
changes to angular.
2023-06-19 23:41:40 +02:00
b21284f640 minor: add payment
Admins can now add payments for their users.
2023-05-28 09:07:54 +02:00
228 changed files with 6207 additions and 2247 deletions

View File

@ -4,19 +4,47 @@ on:
tags:
- '*'
jobs:
build:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Archive Server
uses: thedoctor0/zip-release@master
- uses: https://gitea.com/actions/checkout@master
- name: Zip Artifacts
uses: https://github.com/thedoctor0/zip-release@master
with:
type: 'zip'
filename: 'server.zip'
exclusions: '*.git*'
- name: Release Archive
uses: ncipollo/release-action@v1
uses: https://gitea.com/actions/gitea-release-action@v1
with:
allowUpdates: true
artifacts: "server.zip"
token: ${{ secrets.GITHUB_TOKEN }}
server_url: https://gitea.wpgcommunity.net
files: 'server.zip'
docker:
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/actions/checkout@master
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ vars.DOCKER_REPO }}/costhive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
config-inline: |
[registry."${{ vars.DOCKER_REPO }}"]
http = true
insecure = true
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.DOCKER_REPO }}
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build and push
uses: https://github.com/docker/build-push-action@v6
with:
push: true
context: .
tags: ${{ steps.meta.outputs.tags }}

10
.gitignore vendored
View File

@ -339,7 +339,9 @@ pip-selfcheck.json
# Misc
config.yaml
scans.json
test_*.*
test.*
tests*
*.db
.vscode
*.tar
@ -347,4 +349,10 @@ test.*
.env*
.flaskenv*
!.env.project
!.env.vault
!.env.vault
*.ps1
*.pdf
*.backup
Dockerfile.*
docker-compose.*
*debug.py

View File

@ -1,23 +1,25 @@
FROM python@sha256:075fe10ae13ea0f081540bead850eeb7b6c71d07ed4766d75f8529abd0101c44
# python:3.10.10-slim-bullseye
FROM python@sha256:c66cf219ac0083a9af2ff90e16530f16cd503c59eb7909feb3b8f3524dc1a87e
# python:3.12.2-slim-bullseye (amd64)
RUN useradd costhive
WORKDIR /home/costhive
RUN apt update && apt -y upgrade
RUN apt install -y libpq-dev gcc g++ swig make
RUN apt update && apt -y upgrade; \
apt install -y libpq-dev gcc g++ swig make cmake m4; \
rm -rf /var/lib/apt/lists
COPY boot.sh requirements.txt ./
RUN python -m venv venv
RUN venv/bin/pip install -r requirements.txt
RUN venv/bin/pip install gunicorn
RUN chmod +x boot.sh
COPY boot.sh backend/requirements.txt ./
RUN python -m venv venv; \
venv/bin/pip install --upgrade pip; \
venv/bin/pip install wheel gunicorn; \
venv/bin/pip install -r requirements.txt
COPY backend backend
ENV FLASK_APP run.py
ENV FLASK_APP=run.py
RUN chown -R costhive:costhive ./
RUN chmod +x boot.sh; \
chown -R costhive:costhive .
USER costhive

12
Pipfile
View File

@ -1,12 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask-cors = "*"
[dev-packages]
[requires]
python_version = "3.10"

141
Pipfile.lock generated
View File

@ -1,141 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "2f46680952b8ccfaf093f8ef1315053afb3d2e15fc244d50f313d0961890dfc3"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.6"
},
"flask": {
"hashes": [
"sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d",
"sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"
],
"markers": "python_version >= '3.7'",
"version": "==2.2.3"
},
"flask-cors": {
"hashes": [
"sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438",
"sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"
],
"index": "pypi",
"version": "==3.0.10"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"markupsafe": {
"hashes": [
"sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
"sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
"sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
"sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
"sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
"sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
"sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
"sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
"sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
"sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
"sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
"sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
"sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
"sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
"sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
"sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
"sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
"sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
"sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
"sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
"sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
"sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
"sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
"sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
"sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
"sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
"sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
"sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
"sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
"sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
"sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
"sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
"sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
"sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
"sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
"sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
"sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
"sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
"sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
"sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
"sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
"sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
"sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
"sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
"sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
"sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
"sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
"sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
"sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
"sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"werkzeug": {
"hashes": [
"sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe",
"sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"
],
"markers": "python_version >= '3.7'",
"version": "==2.2.3"
}
},
"develop": {}
}

View File

@ -7,20 +7,25 @@ name = "pypi"
email-validator = "*"
flask = "*"
flask-bootstrap = "*"
flask-cors = "*"
flask-login = "*"
flask-mail = "*"
flask-marshmallow = "*"
flask-migrate = "*"
flask-sqlalchemy = "*"
flask-wtf = "*"
marshmallow-sqlalchemy = "*"
psycopg2-binary = "*"
pyjwt = "*"
python-dotenv = "*"
pymupdf = "*"
requests = "*"
sqlalchemy-utils = "*"
marshmallow = "*"
wtforms-sqlalchemy = "*"
pytest = "*"
[dev-packages]
[requires]
python_version = "3.10"
python_full_version = "3.10.10"
python_version = "3.11"
python_full_version = "3.11.4"

846
backend/Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
os.environ["TESSDATA_PREFIX"] = os.path.join(basedir, 'tessdata')
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or "s0m37h!n6-obfu5c471ng"
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
@ -18,4 +19,5 @@ class Config(object):
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['postmaster@wpgcommunity.net']
POSTS_PER_PAGE = 10
POSTS_PER_PAGE = 15
RECEIPT_FOLDER = f"{basedir}/../PDFReceipts"

Binary file not shown.

View File

@ -0,0 +1 @@
tessedit_create_alto 1

View File

@ -0,0 +1,7 @@
tessedit_ambigs_training 1
load_freq_dawg 0
load_punc_dawg 0
load_system_dawg 0
load_number_dawg 0
ambigs_debug_level 3
load_fixed_length_dawgs 0

View File

@ -0,0 +1 @@
tessedit_zero_rejection T

View File

@ -0,0 +1,5 @@
load_bigram_dawg True
tessedit_enable_bigram_correction True
tessedit_bigram_debug 3
save_raw_choices True
save_alt_choices True

View File

@ -0,0 +1,12 @@
disable_character_fragments T
file_type .bl
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_resegment_from_boxes T
tessedit_train_from_boxes T
textord_no_rejects T

View File

@ -0,0 +1,13 @@
file_type .bl
#tessedit_use_nn F
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_resegment_from_boxes T
tessedit_train_from_boxes T
#textord_repeat_extraction F
textord_no_rejects T

View File

@ -0,0 +1 @@
tessedit_char_whitelist 0123456789-.

View File

@ -0,0 +1 @@
tessedit_write_images T

View File

@ -0,0 +1,2 @@
tessedit_create_hocr 1
hocr_font_info 0

View File

@ -0,0 +1,2 @@
interactive_display_mode T
tessedit_display_outwords T

View File

@ -0,0 +1,4 @@
textord_skewsmooth_offset 8
textord_skewsmooth_offset2 8
textord_merge_desc 0.5
textord_no_rejects 1

View File

@ -0,0 +1,2 @@
tessedit_resegment_from_line_boxes 1
tessedit_make_boxes_from_boxes 1

View File

@ -0,0 +1 @@
debug_file tesseract.log

View File

@ -0,0 +1,11 @@
file_type .bl
textord_fast_pitch_test T
tessedit_zero_rejection T
tessedit_minimal_rejection F
tessedit_write_rep_codes F
edges_children_fix F
edges_childarea 0.65
edges_boxarea 0.9
tessedit_train_line_recognizer T
textord_no_rejects T
tessedit_init_config_only T

View File

@ -0,0 +1 @@
tessedit_create_lstmbox 1

View File

@ -0,0 +1,4 @@
stopper_debug_level 1
classify_debug_level 1
segsearch_debug_level 1
language_model_debug_level 3

View File

@ -0,0 +1 @@
tessedit_create_boxfile 1

View File

@ -0,0 +1 @@
tessedit_create_pdf 1

View File

@ -0,0 +1 @@
debug_file /dev/null

View File

@ -0,0 +1,2 @@
tessedit_resegment_from_boxes 1
tessedit_make_boxes_from_boxes 1

View File

@ -0,0 +1,12 @@
textord_show_blobs 0
textord_debug_tabfind 3
textord_tabfind_show_partitions 1
textord_tabfind_show_initial_partitions 1
textord_tabfind_show_columns 1
textord_tabfind_show_blocks 1
textord_tabfind_show_initialtabs 1
textord_tabfind_show_finaltabs 1
textord_tabfind_show_strokewidths 1
textord_tabfind_show_vlines 0
textord_tabfind_show_images 1
tessedit_dump_pageseg_images 0

View File

@ -0,0 +1 @@
tessedit_create_tsv 1

View File

@ -0,0 +1,3 @@
# This config file should be used with other config files which create renderers.
# usage example: tesseract eurotext.tif eurotext txt hocr pdf
tessedit_create_txt 1

View File

@ -0,0 +1,2 @@
tessedit_write_unlv 1
unlv_tilde_crunching T

View File

@ -0,0 +1 @@
tessedit_create_wordstrbox 1

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
1-\d\d\d-GOOG-411
www.\n\\\*.com

View File

@ -0,0 +1,5 @@
the
quick
brown
fox
jumped

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
# No content needed as all defaults are correct.

View File

@ -0,0 +1,2 @@
chop_enable 0
wordrec_enable_assoc 0

View File

@ -0,0 +1,7 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
classify_enable_adaptive_debugger 1
matcher_debug_flags 6
matcher_debug_level 1

View File

@ -0,0 +1,12 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
classify_enable_adaptive_debugger 1
matcher_debug_flags 6
matcher_debug_level 1
wordrec_display_splits 0
wordrec_display_all_blobs 1
wordrec_display_segmentations 2
classify_debug_level 1

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,9 @@
#################################################
# Adaptive Matcher Using PreAdapted Templates
#################################################
wordrec_display_splits 0
wordrec_display_all_blobs 1
wordrec_display_segmentations 2
classify_debug_level 1
stopper_debug_level 1

View File

@ -0,0 +1,34 @@
"""Add table for logintoken de-/activation dates
Revision ID: 015f4256bb4c
Revises: 9a8c73f0ab11
Create Date: 2023-11-19 20:52:03.377745
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '015f4256bb4c'
down_revision = '9a8c73f0ab11'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('login_token_dates',
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('activation_date', sa.Date(), server_default=sa.text('now()'), nullable=False),
sa.Column('deactivation_date', sa.Date(), nullable=True),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('token', 'activation_date')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('login_token_dates')
# ### end Alembic commands ###

View File

@ -0,0 +1,54 @@
"""receipt id serial
Revision ID: 0fa2ef37e440
Revises: f6f97ed9c053
Create Date: 2023-07-25 21:26:25.353435
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0fa2ef37e440'
down_revision = 'f6f97ed9c053'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.execute("CREATE SEQUENCE receipt_id_seq;")
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.BIGINT(),
type_=sa.Integer(),
existing_nullable=False,
server_default=sa.sql.func.next_value(sa.Sequence('receipt_id_seq')))
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.BIGINT(),
type_=sa.Integer(),
existing_nullable=False)
op.execute("ALTER SEQUENCE receipt_id_seq OWNED BY receipt.id;")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.Integer(),
type_=sa.BIGINT(),
existing_nullable=False)
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.Integer(),
type_=sa.BIGINT(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""new table for bought entries with unknown items 2
Revision ID: 2a64d3b9235a
Revises: 015f4256bb4c
Create Date: 2024-06-02 13:19:59.901053
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2a64d3b9235a'
down_revision = '015f4256bb4c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('bought_with_unknown_item',
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('item', sa.BigInteger(), nullable=False),
sa.Column('date', sa.Date(), nullable=False),
sa.Column('amount', sa.SmallInteger(), nullable=False),
sa.ForeignKeyConstraint(['token'], ['login_token.token'], ),
sa.PrimaryKeyConstraint('token', 'item', 'date')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('bought_with_unknown_item')
# ### end Alembic commands ###

View File

@ -0,0 +1,32 @@
"""Remove accepted from receiptitem
Revision ID: 36f532800705
Revises: cdbe3f5e3b30
Create Date: 2023-11-11 10:45:44.652161
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '36f532800705'
down_revision = 'cdbe3f5e3b30'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.drop_column('accepted')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('accepted', sa.BOOLEAN(), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""Delete unnecessary columns
Revision ID: 4fec22c35530
Revises: 610854a6e2a0
Create Date: 2023-05-28 08:10:27.689070
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4fec22c35530'
down_revision = '610854a6e2a0'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bought', schema=None) as batch_op:
batch_op.drop_column('registered')
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.drop_column('paid')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('login_token', schema=None) as batch_op:
batch_op.add_column(sa.Column('paid', sa.BIGINT(), server_default=sa.text("'0'::bigint"), autoincrement=False, nullable=False))
with op.batch_alter_table('bought', schema=None) as batch_op:
batch_op.add_column(sa.Column('registered', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@ -26,7 +26,7 @@ def upgrade():
sa.PrimaryKeyConstraint('user', 'establishment')
)
op.create_table('payment',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('id', sa.BigInteger(), nullable=False, autoincrement=True),
sa.Column('token', sa.String(length=15), nullable=False),
sa.Column('date', sa.Date(), server_default=sa.text('now()'), nullable=False),
sa.Column('amount', sa.BigInteger(), server_default='0', nullable=False),

View File

@ -0,0 +1,38 @@
"""raise password char length
Revision ID: 782a2409df41
Revises: 926395732c3e
Create Date: 2025-06-03 21:01:23.169897
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '782a2409df41'
down_revision = '926395732c3e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.alter_column('password_hash',
existing_type=sa.VARCHAR(length=128),
type_=sa.String(length=255),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.alter_column('password_hash',
existing_type=sa.String(length=255),
type_=sa.VARCHAR(length=128),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""raise bonid digits
Revision ID: 926395732c3e
Revises: 2a64d3b9235a
Create Date: 2024-08-24 10:33:39.109944
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '926395732c3e'
down_revision = '2a64d3b9235a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('bonid',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.Numeric(precision=28, scale=0),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('bonid',
existing_type=sa.Numeric(precision=28, scale=0),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""add receiptitem names
Revision ID: 9a8c73f0ab11
Revises: 36f532800705
Create Date: 2023-11-18 23:38:56.865780
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9a8c73f0ab11'
down_revision = '36f532800705'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('name', sa.String(), nullable=True))
op.execute("UPDATE receipt_item SET name='Unknown' WHERE name IS NULL;")
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('name', nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.drop_column('name')
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""Remove item.receiptitem relationship
Revision ID: cdbe3f5e3b30
Revises: 0fa2ef37e440
Create Date: 2023-09-19 19:02:23.003034
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'cdbe3f5e3b30'
down_revision = '0fa2ef37e440'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.INTEGER(),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('price', sa.SmallInteger(), nullable=False))
batch_op.alter_column('receipt',
existing_type=sa.INTEGER(),
type_=sa.BigInteger(),
existing_nullable=False)
batch_op.alter_column('item',
existing_type=sa.BIGINT(),
type_=sa.SmallInteger(),
existing_nullable=False)
batch_op.drop_constraint('item_receipt_item_fkey', type_='foreignkey')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.create_foreign_key('item_receipt_item_fkey', 'item', ['item'], ['id'])
batch_op.alter_column('item',
existing_type=sa.SmallInteger(),
type_=sa.BIGINT(),
existing_nullable=False)
batch_op.alter_column('receipt',
existing_type=sa.BigInteger(),
type_=sa.INTEGER(),
existing_nullable=False)
batch_op.drop_column('price')
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.BigInteger(),
type_=sa.INTEGER(),
existing_nullable=False,
autoincrement=True)
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""receipt-id reorganized
Revision ID: f6f97ed9c053
Revises: 4fec22c35530
Create Date: 2023-07-02 14:16:06.260479
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f6f97ed9c053'
down_revision = '4fec22c35530'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('accepted', sa.Boolean(), nullable=False))
batch_op.drop_constraint("item_receipt_receipt_fkey")
batch_op.alter_column('receipt',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False)
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.add_column(sa.Column('bonid', sa.Numeric(precision=24, scale=0), nullable=True))
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.execute("UPDATE receipt SET bonid=id;")
batch_op.alter_column('id',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
batch_op.create_unique_constraint(None, ['bonid'])
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.create_foreign_key(None, 'receipt', ['receipt'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False)
batch_op.drop_column('accepted')
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
batch_op.alter_column('id',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False,
autoincrement=True)
batch_op.drop_column('bonid')
# ### end Alembic commands ###

View File

@ -0,0 +1,18 @@
from .payment import Payment
from .receipt_item import ReceiptItem
from .receipt import Receipt
from .login_token import LoginToken
from .bought import Bought
from .bought_with_unknown_item import BoughtWithUnknownItem
from .establishment import Establishment
from .establishment_candidate import EstablishmentCandidate
from .user import User
from .amount_change import AmountChange
from .price_change import PriceChange
from .category import Category
from .item import Item
from .brand import Brand
from .item_category import item_category
from .login_token_dates import LoginTokenDates
from .schemas import *

View File

@ -1,10 +1,13 @@
from src import db
from datetime import date
from src import db
class AmountChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True, server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True, server_default=str(date(2021, 12, 1)))
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True,
server_default=str(date(2021, 12, 1)))
amount = db.Column(db.SmallInteger, nullable=False, server_default=str(1))
def __repr__(self) -> str:
return f"<Amount_Change {self.item} ({self.date})>"
return f"<Amount_Change {self.item} ({self.date})>"

13
backend/models/bought.py Normal file
View File

@ -0,0 +1,13 @@
from src import db
class Bought(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Bought Object>"

View File

@ -0,0 +1,14 @@
from src import db
from .bought import Bought
class BoughtWithUnknownItem(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.BigInteger, primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True)
amount = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Bought Object>"

View File

@ -1,10 +1,14 @@
from src import db
class Brand(db.Model):
id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False)
Item = db.relationship("Item", backref='Brand', lazy='dynamic')
def __repr__(self) -> str:
return f"<Brand {self.id} ({self.name})>"
return f"<Brand {self.id} ({self.name})>"
def __str__(self) -> str:
return f"{self.name}"

View File

@ -0,0 +1,13 @@
from src import db
from .item_category import item_category
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False, unique=True)
Item = db.relationship("Item", secondary=item_category,
lazy="dynamic", back_populates="Category")
def __repr__(self) -> str:
return f"<Category {self.id} ({self.name})>"

View File

@ -0,0 +1,17 @@
from src import db
class Establishment(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String(64), nullable=False)
owner = db.Column(db.ForeignKey('user.id'), nullable=False)
LoginToken = db.relationship(
"LoginToken", backref='Establishment', lazy='dynamic')
Bought = db.relationship("Bought", secondary="login_token",
lazy='dynamic', overlaps="Establishment,LoginToken,Bought")
EstablishmentCandidate = db.relationship(
"EstablishmentCandidate", backref='Establishment', lazy='dynamic')
def __repr__(self) -> str:
return f"<Establishment {self.id} ({self.name})>"

View File

@ -1,8 +1,10 @@
from src import db
class EstablishmentCandidate(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True)
establishment = db.Column(db.ForeignKey('establishment.id'), primary_key=True)
establishment = db.Column(db.ForeignKey(
'establishment.id'), primary_key=True)
def __repr__(self) -> str:
return f"<EstablishmentCandidate {self.user} ({self.establishment})>"
return f"<EstablishmentCandidate {self.user} ({self.establishment})>"

24
backend/models/item.py Normal file
View File

@ -0,0 +1,24 @@
from src import db, ma
from .item_category import item_category
class Item(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=False)
name = db.Column(db.String(64), nullable=False)
brand = db.Column(db.ForeignKey('brand.id'), nullable=False,
server_onupdate=db.FetchedValue())
description = db.Column(db.Text, nullable=False)
AmountChange = db.relationship(
"AmountChange", backref='Item', lazy='dynamic')
Bought = db.relationship("Bought", backref='Item', lazy='dynamic')
Category = db.relationship(
"Category", secondary=item_category, lazy="dynamic", back_populates="Item")
PriceChange = db.relationship(
"PriceChange", backref='Item', lazy='dynamic')
def __repr__(self) -> str:
return f"<Item {self.id} ({self.name})>"
def __str__(self) -> str:
return f"({self.id}) {self.description}"

View File

@ -1,12 +1,16 @@
from src import db
class LoginToken(db.Model):
user = db.Column(db.ForeignKey('user.id'), primary_key=True, server_onupdate=db.FetchedValue())
establishment = db.Column(db.ForeignKey('establishment.id'), primary_key=True, server_onupdate=db.FetchedValue())
user = db.Column(db.ForeignKey('user.id'), primary_key=True,
server_onupdate=db.FetchedValue())
establishment = db.Column(db.ForeignKey(
'establishment.id'), primary_key=True, server_onupdate=db.FetchedValue())
token = db.Column(db.String(15), nullable=False, unique=True)
LoginTokenDates = db.relationship("LoginTokenDates", backref='LoginToken', lazy='dynamic')
Payment = db.relationship("Payment", backref='LoginToken', lazy='dynamic')
Receipt = db.relationship("Receipt", backref='LoginToken', lazy='dynamic')
def __repr__(self) -> str:
return f"<LoginToken {self.token}>"
return f"<LoginToken {self.token}>"

View File

@ -0,0 +1,13 @@
from src import db
class LoginTokenDates(db.Model):
token = db.Column(db.ForeignKey('login_token.token'),
nullable = False, primary_key=True,
server_onupdate=db.FetchedValue())
activation_date = db.Column(db.Date,
nullable=False, primary_key=True,
server_default=db.func.now())
deactivation_date = db.Column(db.Date, nullable=True)
def __repr__(self) -> str:
return f"<LoginTokenDates {self.token, self.activation_date, self.deactivation_date}>"

12
backend/models/payment.py Normal file
View File

@ -0,0 +1,12 @@
from src import db
class Payment(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
token = db.Column(db.ForeignKey('login_token.token'),
server_onupdate=db.FetchedValue(), nullable=False)
date = db.Column(db.Date, nullable=False, server_default=db.func.now())
amount = db.Column(db.BigInteger, nullable=False, server_default=str(0))
def __repr__(self) -> str:
return f"<Payment {self.id}>"

View File

@ -0,0 +1,13 @@
from datetime import date
from src import db
class PriceChange(db.Model):
item = db.Column(db.ForeignKey('item.id'), primary_key=True,
server_onupdate=db.FetchedValue())
date = db.Column(db.Date, primary_key=True,
server_default=str(date(2021, 12, 1)))
price = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<Price_Change {self.item} ({self.date})>"

17
backend/models/receipt.py Normal file
View File

@ -0,0 +1,17 @@
from src import db
class Receipt(db.Model):
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
date = db.Column(db.Date, nullable=False)
bonid = db.Column(db.Numeric(precision=28, scale=0), unique=True)
from_user = db.Column(db.ForeignKey("login_token.token"),
server_onupdate=db.FetchedValue())
registered = db.Column(db.Boolean, nullable=False,
server_default=str(False))
ReceiptItem = db.relationship(
"ReceiptItem", backref='Receipt', lazy='dynamic')
def __repr__(self) -> str:
return f"<Receipt {self.id}>"

View File

@ -0,0 +1,13 @@
from src import db
class ReceiptItem(db.Model):
receipt = db.Column(db.ForeignKey("receipt.id"),
primary_key=True, server_onupdate=db.FetchedValue())
item = db.Column(db.SmallInteger, primary_key=True, nullable=False)
name = db.Column(db.String, nullable=False)
amount = db.Column(db.SmallInteger, nullable=False, default=str(1))
price = db.Column(db.SmallInteger, nullable=False)
def __repr__(self) -> str:
return f"<ReceiptItem {self.receipt}: {self.item}>"

View File

@ -0,0 +1,13 @@
from .brand import BrandSchema
from .item import ItemSchema
from .category import CategorySchema
from .price_change import PriceChangeSchema
from .amount_change import AmountChangeSchema
from .user import UserSchema
from .establishment import EstablishmentSchema
from .establishment_candidate import EstablishmentCandidateSchema
from .login_token import LoginTokenSchema
from .bought import BoughtSchema
from .receipt import ReceiptSchema
from .receipt_item import ReceiptItemSchema
from .payment import PaymentSchema

View File

@ -0,0 +1,13 @@
from src import ma
from ..amount_change import AmountChange
from .item import ItemSchema
class AmountChangeSchema(ma.SQLAlchemySchema):
class Meta:
model = AmountChange
include_fk = True
Item = ma.Nested(ItemSchema)
date = ma.auto_field()
amount = ma.auto_field()

View File

@ -0,0 +1,15 @@
from src import ma
from ..bought import Bought
from .login_token import LoginTokenSchema
from .item import ItemSchema
class BoughtSchema(ma.SQLAlchemySchema):
class Meta:
model = Bought
include_fk = True
LoginToken = ma.Nested(LoginTokenSchema)
Item = ma.Nested(ItemSchema)
date = ma.auto_field()
amount = ma.auto_field()

View File

@ -0,0 +1,10 @@
from src import ma
from ..brand import Brand
class BrandSchema(ma.SQLAlchemySchema):
class Meta:
model = Brand
id = ma.auto_field()
name = ma.auto_field()

View File

@ -0,0 +1,11 @@
# from marshmallow import Schema, fields
from src import ma
from ..category import Category
class CategorySchema(ma.SQLAlchemySchema):
class Meta:
model = Category
id = ma.auto_field()
name = ma.auto_field()

View File

@ -0,0 +1,13 @@
from src import ma
from ..establishment import Establishment
from .user import UserSchema
class EstablishmentSchema(ma.SQLAlchemySchema):
class Meta:
model = Establishment
include_fk = True
id = ma.auto_field()
name = ma.auto_field()
User = ma.Nested(UserSchema)

View File

@ -0,0 +1,13 @@
from src import ma
from ..establishment_candidate import EstablishmentCandidate
from .establishment import EstablishmentSchema
from .user import UserSchema
class EstablishmentCandidateSchema(ma.SQLAlchemySchema):
class Meta:
model = EstablishmentCandidate
include_fk = True
User = ma.Nested(UserSchema)
Establishment = ma.Nested(EstablishmentSchema)

View File

@ -0,0 +1,18 @@
from src import ma
from ..item import Item
from .brand import BrandSchema
class ItemSchema(ma.SQLAlchemySchema):
class Meta:
model = Item
include_fk = True
id = ma.auto_field()
name = ma.auto_field()
description = ma.auto_field()
Brand = ma.Nested(BrandSchema)
PriceChange = ma.List(ma.Nested(lambda: PriceChangeSchema(exclude=("Item",))))
AmountChange = ma.List(ma.Nested(lambda: AmountChangeSchema(exclude=("Item",))))
from .price_change import PriceChangeSchema
from .amount_change import AmountChangeSchema

View File

@ -0,0 +1,14 @@
from src import ma
from ..login_token import LoginToken
from .establishment import EstablishmentSchema
from .user import UserSchema
class LoginTokenSchema(ma.SQLAlchemySchema):
class Meta:
model = LoginToken
include_fk = True
User = ma.Nested(UserSchema)
Establishment = ma.Nested(EstablishmentSchema)
token = ma.auto_field()

View File

@ -0,0 +1,14 @@
from src import ma
from ..payment import Payment
from .login_token import LoginTokenSchema
class PaymentSchema(ma.SQLAlchemySchema):
class Meta:
model = Payment
include_fk = True
id = ma.auto_field()
LoginToken = ma.Nested(LoginTokenSchema)
date = ma.auto_field()
amount = ma.auto_field()

View File

@ -0,0 +1,12 @@
from src import ma
from .item import ItemSchema
from ..price_change import PriceChange
class PriceChangeSchema(ma.SQLAlchemySchema):
class Meta:
model = PriceChange
include_fk = True
Item = ma.Nested(ItemSchema)
date = ma.auto_field()
price = ma.auto_field()

View File

@ -0,0 +1,15 @@
from src import ma
from ..receipt import Receipt
from .login_token import LoginTokenSchema
class ReceiptSchema(ma.SQLAlchemySchema):
class Meta:
model = Receipt
include_fk = True
id = ma.auto_field()
date = ma.auto_field()
bonid = ma.auto_field()
registered = ma.auto_field()
LoginToken = ma.Nested(LoginTokenSchema)

View File

@ -0,0 +1,16 @@
from src import ma
from ..receipt_item import ReceiptItem
from .item import ItemSchema
from .receipt import ReceiptSchema
class ReceiptItemSchema(ma.SQLAlchemySchema):
class Meta:
model = ReceiptItem
include_fk = True
Receipt = ma.Nested(ReceiptSchema)
item = ma.auto_field()
name = ma.auto_field()
amount = ma.auto_field()
price = ma.auto_field()

View File

@ -0,0 +1,10 @@
from src import ma
from ..user import User
class UserSchema(ma.SQLAlchemySchema):
class Meta:
model = User
id = ma.auto_field()
email = ma.auto_field()
password_hash = ma.auto_field()

View File

@ -1,21 +1,23 @@
import jwt
from src import db, login
from flask import current_app
from flask_login import UserMixin
from marshmallow import Schema, fields
from src import db, login
from time import time
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.BigInteger, primary_key=True)
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
email = db.Column(db.String(255), nullable=False, unique=True)
password_hash = db.Column(db.String(128), nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
LoginToken = db.relationship("LoginToken", backref='User', lazy='dynamic')
Bought = db.relationship("Bought", secondary="login_token",
lazy='dynamic', overlaps="User,LoginToken")
EstablishmentCandidate = db.relationship("EstablishmentCandidate", backref='User', lazy='dynamic')
Establishment = db.relationship("Establishment", backref="User", lazy="dynamic")
lazy='dynamic', overlaps="User,LoginToken")
EstablishmentCandidate = db.relationship(
"EstablishmentCandidate", backref='User', lazy='dynamic')
Establishment = db.relationship(
"Establishment", backref="User", lazy="dynamic")
def set_password(self, password):
self.password_hash = generate_password_hash(password)
@ -40,11 +42,7 @@ class User(UserMixin, db.Model):
def __repr__(self) -> str:
return f"<User {self.id} ({self.email})>"
class UserSchema(Schema):
id = fields.Number()
email = fields.Str()
password_hash = fields.Str()
@login.user_loader
def load_user(id):
return User.query.get(int(id))
return User.query.get(int(id))

44
backend/requirements.txt Normal file
View File

@ -0,0 +1,44 @@
-i https://pypi.org/simple
alembic==1.12.1; python_version >= '3.7'
blinker==1.7.0; python_version >= '3.8'
certifi==2023.7.22; python_version >= '3.6'
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
click==8.1.7; python_version >= '3.7'
colorama==0.4.6; sys_platform == 'win32'
dnspython==2.4.2; python_version >= '3.8' and python_version < '4.0'
dominate==2.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
email-validator==2.1.0.post1
flask==3.0.0
flask-bootstrap==3.3.7.1
flask-cors==4.0.0
flask-login==0.6.3
flask-mail==0.9.1
flask-marshmallow==0.15.0
flask-migrate==4.0.5
flask-sqlalchemy==3.1.1
flask-wtf==1.2.1
greenlet==3.0.1; platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))
idna==3.4; python_version >= '3.5'
iniconfig==2.0.0; python_version >= '3.7'
itsdangerous==2.1.2; python_version >= '3.7'
jinja2==3.1.2; python_version >= '3.7'
mako==1.3.0; python_version >= '3.8'
markupsafe==2.1.3; python_version >= '3.7'
marshmallow==3.20.1; python_version >= '3.8'
marshmallow-sqlalchemy==0.29.0
packaging==23.2; python_version >= '3.7'
pluggy==1.3.0; python_version >= '3.8'
psycopg2-binary==2.9.9
pyjwt==2.8.0
pymupdf==1.23.6
pytest==7.4.3
python-dotenv==1.0.0
requests==2.31.0
sqlalchemy==2.0.23; python_version >= '3.7'
sqlalchemy-utils==0.41.1
typing-extensions==4.8.0; python_version >= '3.8'
urllib3==2.0.7; python_version >= '3.7'
visitor==0.1.3
werkzeug==3.0.1; python_version >= '3.8'
wtforms==3.1.1; python_version >= '3.8'
wtforms-sqlalchemy==0.3

View File

@ -1,10 +1,12 @@
from src import create_app, db
from src.models import Bought, Establishment, Item, LoginToken, Receipt, User, UserSchema
from models import *
app = create_app()
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User, 'Bought': Bought, 'Item': Item,
"LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt,
'UserSchema': UserSchema}
return {'db': db, 'User': User, 'Bought': Bought, "Brand": Brand, 'Item': Item,
"LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt,
"brandschema": BrandSchema(), "itemschema": ItemSchema(), "testitem": Item.query.get(4311501628485),
"testuser": User.query.get(1)}

View File

@ -4,6 +4,7 @@ from flask_bootstrap import Bootstrap
from flask_cors import CORS
from flask_login import LoginManager
from flask_mail import Mail
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from logging import getLogger
@ -31,17 +32,21 @@ cors = CORS()
db = SQLAlchemy()
login = LoginManager()
login.login_view = 'auth.web_login'
ma = Marshmallow()
mail = Mail()
migrate = Migrate()
migrate = Migrate(transaction_per_migration=True)
def create_app(config_class=Config):
app = Flask(__name__)
if not exists(config_class.RECEIPT_FOLDER):
makedirs(config_class.RECEIPT_FOLDER)
app = Flask(__name__, template_folder="../web/templates", static_folder="../web/static")
app.config.from_object(config_class)
bootstrap.init_app(app)
cors.init_app(app)
db.init_app(app)
login.init_app(app)
ma.init_app(app)
mail.init_app(app)
migrate.init_app(app, db, render_as_batch=True)
@ -62,4 +67,4 @@ def create_app(config_class=Config):
return app
from src.models import *
from models import *

View File

@ -2,5 +2,5 @@ from flask import Blueprint
bp = Blueprint('api', __name__, url_prefix='/api')
from src.api.v1 import bp as bp_v1
from .v1 import bp as bp_v1
bp.register_blueprint(bp_v1)

View File

@ -2,4 +2,4 @@ from flask import Blueprint
bp = Blueprint('v1', __name__, url_prefix='/v1')
from src.api.v1 import routes
from . import routes

View File

@ -1,6 +1,6 @@
from src import LOGGER
from src.api.v1 import bp
from src.models.login_token import LoginToken
from models.login_token import LoginToken
from src.utils import database_utils
from flask import abort, request
from flask.json import jsonify

View File

@ -2,4 +2,4 @@ from flask import Blueprint
bp = Blueprint('auth', __name__, url_prefix='/auth')
from src.auth import forms, routes
from . import forms, routes

View File

@ -1,4 +1,4 @@
from src.models.user import User
from models.user import User
from flask_wtf import FlaskForm
from wtforms import BooleanField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError

Some files were not shown because too many files have changed in this diff Show More