added users and groups
This commit is contained in:
parent
cf8559aa13
commit
e07ee822fb
|
@ -0,0 +1,136 @@
|
||||||
|
<mxfile host="Electron" modified="2024-10-25T22:45:42.822Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="5By3CPPGDMnT9W5b2oTL" version="22.0.2" type="device">
|
||||||
|
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||||
|
<mxGraphModel dx="499" dy="882" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-0" value="Users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry y="280" width="250" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-1" value="ID SERIAL PRIMARY_KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-2" value="username VARCHAR(32) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-3" value="password CHAR(60)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-16" value="groups INT[]" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-4" value="Abstract_products" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="130" width="250" height="270" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-5" value="ID SERIAL PRIMARY_KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-20" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-19" value="barcode CHAR(13)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-6" value="name VARCHAR(128)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-8" value="net_weight REAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="150" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-9" value="image_filename CHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="180" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-10" value="category INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="210" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-11" value="unit INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="240" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-12" value="Groups" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="330" y="470" width="180" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-13" value="ID SERIAL PRIMARY_KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-14" value="name VARCHAR(64) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-15" value="admin_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-36" value="password VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-1" target="JJCkPNM4InE_l2XlKl3H-15">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="510" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="410" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-18" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmany;startArrow=ERmany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-16" target="JJCkPNM4InE_l2XlKl3H-13">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="330" y="530" as="sourcePoint" />
|
||||||
|
<mxPoint x="430" y="430" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-21" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-20">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="620" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="520" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-22" value="Categories" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="670" width="163" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-23" value="ID SERIAL PRIMARY_KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="30" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-25" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="60" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-24" value="name VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="90" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-26" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-25">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="340" y="600" as="sourcePoint" />
|
||||||
|
<mxPoint x="440" y="500" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-27" value="Products<br>" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="930" y="470" width="190" height="210" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-28" value="ID SERIAL PRIMARY_KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="30" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-29" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="60" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-30" value="abstract_product_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="90" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-32" value="amount INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="120" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-33" value="date_of_production DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="150" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-34" value="expiry_date DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="180" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-31" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-29">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="660" y="580" as="sourcePoint" />
|
||||||
|
<mxPoint x="760" y="480" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-35" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-5" target="JJCkPNM4InE_l2XlKl3H-30">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="980" y="340" as="sourcePoint" />
|
||||||
|
<mxPoint x="1080" y="240" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
|
@ -0,0 +1,136 @@
|
||||||
|
<mxfile host="Electron" modified="2024-10-25T23:03:19.789Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="RemdWc7X7YsC5QZmfqTC" version="22.0.2" type="device">
|
||||||
|
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||||
|
<mxGraphModel dx="499" dy="882" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-0" value="Users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry y="280" width="250" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-1" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-2" value="username VARCHAR(32) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-3" value="password CHAR(60)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-16" value="groups INT[]" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-4" value="Abstract_products" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="130" width="250" height="270" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-5" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-20" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-19" value="barcode CHAR(13)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-6" value="name VARCHAR(128)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-8" value="net_weight REAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="150" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-9" value="image_filename CHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="180" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-10" value="category INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="210" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-11" value="unit INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="240" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-12" value="Groups" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="330" y="470" width="180" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-13" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-14" value="name VARCHAR(64) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-15" value="admin_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-36" value="password VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-1" target="JJCkPNM4InE_l2XlKl3H-15">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="510" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="410" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-18" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmany;startArrow=ERmany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-16" target="JJCkPNM4InE_l2XlKl3H-13">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="330" y="530" as="sourcePoint" />
|
||||||
|
<mxPoint x="430" y="430" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-21" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-20">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="620" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="520" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-22" value="Categories" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="670" width="163" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-23" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="30" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-25" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="60" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-24" value="name VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="90" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-26" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-25">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="340" y="600" as="sourcePoint" />
|
||||||
|
<mxPoint x="440" y="500" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-27" value="Products<br>" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="930" y="470" width="190" height="210" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-28" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="30" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-29" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="60" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-30" value="abstract_product_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="90" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-32" value="amount INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="120" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-33" value="date_of_production DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="150" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-34" value="expiry_date DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="180" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-31" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-29">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="660" y="580" as="sourcePoint" />
|
||||||
|
<mxPoint x="760" y="480" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-35" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-5" target="JJCkPNM4InE_l2XlKl3H-30">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="980" y="340" as="sourcePoint" />
|
||||||
|
<mxPoint x="1080" y="240" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"debug": true,
|
||||||
|
"port": "8081",
|
||||||
|
"dbuser": "bsfe",
|
||||||
|
"dbhost": "localhost",
|
||||||
|
"dbport": "5432",
|
||||||
|
"dbpassword": "Ch@NgEME!",
|
||||||
|
"dbname": "bsfe",
|
||||||
|
"secret": "SECRET!11!1!"
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS Users(
|
||||||
|
ID SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(32) UNIQUE,
|
||||||
|
password CHAR(60), -- hashed with salt using bcrypt
|
||||||
|
groups INT[] -- IDs of groups user is a member of
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Groups (
|
||||||
|
ID SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(64) UNIQUE,
|
||||||
|
admin_id INT,
|
||||||
|
password VARCHAR(64) -- password is required to join a group. Set by admin
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Abstract_products (
|
||||||
|
ID SERIAL PRIMARY KEY,
|
||||||
|
group_id INT, -- Abstract products are tied to a group of users they were created by
|
||||||
|
barcode CHAR(13), -- EAN13
|
||||||
|
name VARCHAR(128),
|
||||||
|
net_weight REAL,
|
||||||
|
image_filename CHAR(32), -- generated by taking md5 hash of an image
|
||||||
|
category INT, -- link to a category id
|
||||||
|
unit INT -- link to a unit id, not stored in database due to locale nature, using predefined order instead: kg = 0, g, l, ml, pc.
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Categories (
|
||||||
|
ID SERIAL PRIMARY KEY,
|
||||||
|
group_id INT,
|
||||||
|
name VARCHAR(64)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Products (
|
||||||
|
ID SERIAL PRIMARY KEY,
|
||||||
|
group_id INT,
|
||||||
|
abstract_product_id INT,
|
||||||
|
amount INT,
|
||||||
|
date_of_production DATE,
|
||||||
|
expiry_date DATE
|
||||||
|
);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "bsfe_server",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Server for BarcodeScannerForEmployees",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.foxarmy.org/leca/bsfe_server"
|
||||||
|
},
|
||||||
|
"author": "leca",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"nodemon": "^3.1.7",
|
||||||
|
"pg": "^8.13.1"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
<mxfile host="Electron" modified="2024-10-25T23:03:15.235Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="Q2moAL3BISTiBrmvIWt3" version="22.0.2" type="device">
|
||||||
|
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||||
|
<mxGraphModel dx="499" dy="882" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-0" value="Users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry y="280" width="250" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-1" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-2" value="username VARCHAR(32) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-3" value="password CHAR(60)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-16" value="groups INT[]" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-0">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-4" value="Abstract_products" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="130" width="250" height="270" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-5" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-20" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="60" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-19" value="barcode CHAR(13)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="90" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-6" value="name VARCHAR(128)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-8" value="net_weight REAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="150" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-9" value="image_filename CHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="180" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-10" value="category INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="210" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-11" value="unit INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-4">
|
||||||
|
<mxGeometry y="240" width="250" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-12" value="Groups" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="330" y="470" width="180" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-13" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-14" value="name VARCHAR(64) UNIQUE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-15" value="admin_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-36" value="password VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-12">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-1" target="JJCkPNM4InE_l2XlKl3H-15">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="510" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="410" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-18" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmany;startArrow=ERmany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-16" target="JJCkPNM4InE_l2XlKl3H-13">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="330" y="530" as="sourcePoint" />
|
||||||
|
<mxPoint x="430" y="430" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-21" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-20">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="400" y="620" as="sourcePoint" />
|
||||||
|
<mxPoint x="500" y="520" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-22" value="Categories" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="577" y="670" width="163" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-23" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="30" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-25" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="60" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-24" value="name VARCHAR(64)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-22">
|
||||||
|
<mxGeometry y="90" width="163" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-26" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-25">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="340" y="600" as="sourcePoint" />
|
||||||
|
<mxPoint x="440" y="500" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-27" value="Products<br>" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="930" y="470" width="190" height="210" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-28" value="ID SERIAL PRIMARY KEY" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="30" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-29" value="group_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="60" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-30" value="abstract_product_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="90" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-32" value="amount INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="120" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-33" value="date_of_production DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="150" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-34" value="expiry_date DATE" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="JJCkPNM4InE_l2XlKl3H-27">
|
||||||
|
<mxGeometry y="180" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-31" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-13" target="JJCkPNM4InE_l2XlKl3H-29">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="660" y="580" as="sourcePoint" />
|
||||||
|
<mxPoint x="760" y="480" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="JJCkPNM4InE_l2XlKl3H-35" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="JJCkPNM4InE_l2XlKl3H-5" target="JJCkPNM4InE_l2XlKl3H-30">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="980" y="340" as="sourcePoint" />
|
||||||
|
<mxPoint x="1080" y="240" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
|
@ -0,0 +1,53 @@
|
||||||
|
import GroupService from '../services/group.js';
|
||||||
|
import UserService from '../services/user.js';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import config from '../../config.json' with {type: "json"};
|
||||||
|
import statuses from '../utils/status.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
|
|
||||||
|
const TAG = "/controllers/group.js"
|
||||||
|
|
||||||
|
class GroupController {
|
||||||
|
async create(req, res) {
|
||||||
|
try {
|
||||||
|
let { name } = req.params;
|
||||||
|
|
||||||
|
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
|
||||||
|
let status = await GroupService.create(name, user.login.id);
|
||||||
|
|
||||||
|
log.info(`New group with name ${name} was just created by user ${user.login.username}`);
|
||||||
|
|
||||||
|
UserService.joinGroup(user.login.id, status.id);
|
||||||
|
return res.status(200).send("Successfull");
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/create: ${e}`)); }
|
||||||
|
}
|
||||||
|
async join(req, res) {
|
||||||
|
try {
|
||||||
|
let { id } = req.params;
|
||||||
|
|
||||||
|
await GroupService.getById(id);
|
||||||
|
|
||||||
|
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
|
||||||
|
let status = await UserService.joinGroup(user.login.id, id);
|
||||||
|
|
||||||
|
if (status == statuses.duplicate) return res.status(400).send("Already in group");
|
||||||
|
|
||||||
|
log.info(`User ${user.login.username} has just joined group with ID ${id}`);
|
||||||
|
return res.status(200).send("Successfull");
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/join: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePassword(req, res) {
|
||||||
|
try {
|
||||||
|
let { id } = req.params;
|
||||||
|
let { password } = req.body;
|
||||||
|
|
||||||
|
await GroupService.updatePassword(id, password);
|
||||||
|
log.info(`Password for group with ID ${id} was updated`);
|
||||||
|
return res.status(200).send("Successfull");
|
||||||
|
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/updatePassword ${e}`)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new GroupController();
|
|
@ -0,0 +1,33 @@
|
||||||
|
import UserService from '../services/user.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import genToken from '../utils/jwt.js';
|
||||||
|
|
||||||
|
const TAG = "/controllers/userjs"
|
||||||
|
|
||||||
|
class UserController {
|
||||||
|
async register (req, res) {
|
||||||
|
try {
|
||||||
|
const {username, password} = req.body;
|
||||||
|
|
||||||
|
await UserService.create(username, password);
|
||||||
|
|
||||||
|
log.info(`New user with name ${username} has just registered`);
|
||||||
|
return res.status(200).send("Successfull register")
|
||||||
|
} catch (e) { res.status(500).send(log.unknownError(`${TAG}/register: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(req, res) {
|
||||||
|
try {
|
||||||
|
const {username, password} = req.body;
|
||||||
|
|
||||||
|
const user = await UserService.getByUsername(username);
|
||||||
|
if (!bcrypt.compareSync(password, user.password)) return res.status(401).send("Wrong password");
|
||||||
|
|
||||||
|
const token = genToken(user);
|
||||||
|
return res.status(200).send(token);
|
||||||
|
} catch (e) { res.status(500).send(log.unknownError(`${TAG}/login: ${e}`)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UserController()
|
|
@ -0,0 +1,28 @@
|
||||||
|
import pg from 'pg';
|
||||||
|
import log from './utils/log.js'
|
||||||
|
import fs from 'fs'
|
||||||
|
import config from '../config.json' with {type: "json"};
|
||||||
|
|
||||||
|
const { Pool } = pg;
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
`Connecting to PG database ${config.dbname}@${config.dbhost}:${config.dbport} with credentials:
|
||||||
|
username: ${config.dbuser}
|
||||||
|
password: <hidden>`
|
||||||
|
);
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
user: config.dbuser,
|
||||||
|
host: config.dbhost,
|
||||||
|
port: config.dbport,
|
||||||
|
database: config.dbname,
|
||||||
|
password: config.dbpassword
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug("Database connection successful. Creating tables");
|
||||||
|
|
||||||
|
pool.query(fs.readFileSync('./db_schema.psql').toString());
|
||||||
|
|
||||||
|
log.debug("Tables succesfully created");
|
||||||
|
|
||||||
|
export default pool;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import express from 'express';
|
||||||
|
import UserRouter from './routers/user.js';
|
||||||
|
import GroupRouter from './routers/group.js';
|
||||||
|
// import AdminRouter from './routers/admin.js';
|
||||||
|
import log from './utils/log.js'
|
||||||
|
|
||||||
|
import config from '../config.json' with {type: "json"};
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.urlencoded({extended: true}));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use('/api/user/', UserRouter);
|
||||||
|
app.use('/api/group/', GroupRouter);
|
||||||
|
// app.use('/api/admin/', AdminRouter);
|
||||||
|
|
||||||
|
app.listen(config.port, () => {
|
||||||
|
log.info(`Application has started on port ${config.port}`)
|
||||||
|
})
|
|
@ -0,0 +1,63 @@
|
||||||
|
import log from '../utils/log.js'
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import config from '../../config.json' with {type: "json"};
|
||||||
|
import GroupService from '../services/group.js';
|
||||||
|
|
||||||
|
const TAG = "/middlewares/auth.js"
|
||||||
|
|
||||||
|
const requireUsernameAndPassword = async (req, res, next) => {
|
||||||
|
if (req.method == "OPTIONS") next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {username, password} = req.body;
|
||||||
|
if (!username) return res.status(400).send("Username is required");
|
||||||
|
if (!password) return res.status(400).send("Password is required");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(unknownError(`${TAG}/requireUsernameAndPassword: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticate = async (req, res, next) => {
|
||||||
|
if (req.method == "OPTIONS") next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization.split(' ')[1]
|
||||||
|
if (!token) return res.status(401).send("No authorization token supplied");
|
||||||
|
if (!jwt.verify(token, config.secret)) return res.status(403).send("Authorization token is incorrect");
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authenticate: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizeGroupOwner = async (req, res, next) => {
|
||||||
|
if (req.method == "OPTIONS") next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization.split(' ')[1]
|
||||||
|
if (!token) return res.status(401).send("No authorization token supplied");
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
let user = jwt.decode(token, config.secret)
|
||||||
|
|
||||||
|
let adminId = await GroupService.getAdminId(id);
|
||||||
|
if (user.login.id != adminId) return res.status(403).send("Not your group");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authorizeGroupOwner: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkGroupPassword = async (req, res, next) => {
|
||||||
|
if (req.method == "OPTIONS") next();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { password } = req.body;
|
||||||
|
|
||||||
|
const groupPassword = await GroupService.getPassword(id);
|
||||||
|
|
||||||
|
if (groupPassword != password) return res.status(403).send("Wrong password");
|
||||||
|
next();
|
||||||
|
|
||||||
|
} catch (e) {return res.status(500).send(log.unknownError(`${TAG}/checkGroupPassword: ${e}`));}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { requireUsernameAndPassword, authenticate, authorizeGroupOwner, checkGroupPassword }
|
|
@ -0,0 +1,51 @@
|
||||||
|
import UserService from '../services/user.js';
|
||||||
|
import GroupService from '../services/group.js';
|
||||||
|
import log from '../utils/log.js';
|
||||||
|
import statuses from '../utils/status.js';
|
||||||
|
|
||||||
|
const TAG = "/middlewares/existance.js"
|
||||||
|
|
||||||
|
const usernameExists = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let { username } = req.body;
|
||||||
|
|
||||||
|
let user = await UserService.getByUsername(username);
|
||||||
|
if (!user || user == statuses.not_found) return res.status(404).send("User not found");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/usernameExists: ${e}`)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const usernameDoesntExist = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let { username } = req.body;
|
||||||
|
|
||||||
|
let user = await UserService.getByUsername(username);
|
||||||
|
if (user || user != statuses.not_found) return res.status(400).send("Such username already taken");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/usernameDoesntExist: ${e}`)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupExists = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
let group = await GroupService.getById(id);
|
||||||
|
|
||||||
|
if (!group || group == statuses.not_found) return res.status(404).send("Group not found");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupExists: ${e}`)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupDoesntExist = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
let group = await GroupService.getById(id);
|
||||||
|
|
||||||
|
if (group || group != statuses.not_found) return res.status(400).send("Such group already exists");
|
||||||
|
next();
|
||||||
|
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupDoesntExist: ${e}`)) }
|
||||||
|
}
|
||||||
|
export default { usernameExists, usernameDoesntExist, groupExists, groupDoesntExist }
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Router } from 'express';
|
||||||
|
import auth from '../middlewares/auth.js';
|
||||||
|
import GroupController from '../controllers/group.js'
|
||||||
|
import existance from '../middlewares/existance.js';
|
||||||
|
|
||||||
|
const GroupRouter = new Router();
|
||||||
|
|
||||||
|
GroupRouter.post('/create/:name', auth.authenticate, existance.groupDoesntExist, GroupController.create);
|
||||||
|
GroupRouter.post('/join/:id', auth.authenticate, existance.groupExists, auth.checkGroupPassword, GroupController.join);
|
||||||
|
GroupRouter.post('/password/:id', auth.authenticate, existance.groupExists, auth.authorizeGroupOwner, GroupController.updatePassword)
|
||||||
|
|
||||||
|
export default GroupRouter;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Router } from 'express';
|
||||||
|
import auth from '../middlewares/auth.js';
|
||||||
|
import existance from '../middlewares/existance.js';
|
||||||
|
import UserController from '../controllers/user.js'
|
||||||
|
|
||||||
|
const UserRouter = new Router();
|
||||||
|
|
||||||
|
UserRouter.post('/register', auth.requireUsernameAndPassword, existance.usernameDoesntExist, UserController.register);
|
||||||
|
UserRouter.post('/login', auth.requireUsernameAndPassword, existance.usernameExists, UserController.login);
|
||||||
|
|
||||||
|
|
||||||
|
export default UserRouter;
|
|
@ -0,0 +1,34 @@
|
||||||
|
import db from '../db.js';
|
||||||
|
import status from '../utils/status.js';
|
||||||
|
|
||||||
|
class GroupService {
|
||||||
|
async create(name, creatorId) {
|
||||||
|
let res = await db.query("INSERT INTO groups (name, admin_id) VALUES ($1, $2) RETURNING ID", [name, creatorId]).catch (e => {
|
||||||
|
if (e.code == 23505) { // already exists
|
||||||
|
return status.duplicate;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.rows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id) {
|
||||||
|
let res = (await db.query("SELECT * FROM groups WHERE id = $1", [id]))
|
||||||
|
if (res.rowCount == 0) return status.not_found;
|
||||||
|
return res.rows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAdminId(id) {
|
||||||
|
return (await db.query("SELECT admin_id FROM groups WHERE ID = $1", [id])).rows[0].admin_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePassword(id, password) {
|
||||||
|
return (await db.query("UPDATE groups SET password = $1 WHERE id = $2", [password, id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPassword(id) {
|
||||||
|
return (await db.query("SELECT password FROM groups WHERE id = $1", [id])).rows[0].password;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default new GroupService();
|
|
@ -0,0 +1,36 @@
|
||||||
|
import db from '../db.js'
|
||||||
|
import statuses from '../utils/status.js';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
async create (username, password) {
|
||||||
|
await db.query("INSERT INTO users (username, password) VALUES ($1, $2)", [username, bcrypt.hashSync(password, 12)]).catch (e => {
|
||||||
|
if (e.code == 23505) { // already exists
|
||||||
|
return statuses.duplicate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return statuses.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByUsername(username) {
|
||||||
|
let user = (await db.query("SELECT * FROM Users WHERE username = $1", [username])).rows
|
||||||
|
if (user == undefined) return statuses.not_found
|
||||||
|
return (user[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
return (await db.query("SELECT * FROM Users")).rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async isInGroup(userId, groupId) {
|
||||||
|
return (await db.query("SELECT * FROM Users WHERE ID = $1 AND $2 = ANY(groups)", [userId, groupId])).rowCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinGroup(userId, groupId) {
|
||||||
|
if (await (this.isInGroup(userId, groupId))) return statuses.duplicate;
|
||||||
|
await db.query("UPDATE Users SET groups = array_append(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
|
||||||
|
return statuses.ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UserService();
|
|
@ -0,0 +1,9 @@
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import config from '../../config.json' with {type: "json"};
|
||||||
|
|
||||||
|
const genToken = (login) => {
|
||||||
|
const payload = { login };
|
||||||
|
return jwt.sign(payload, config.secret, {expiresIn: "7d"});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default genToken;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import config from '../../config.json' with {type: "json"};
|
||||||
|
|
||||||
|
const debug = (text) => {
|
||||||
|
if (config.debug) console.debug(`[D] [${Date()}]: ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = (text) => {
|
||||||
|
console.log(`[I] [${Date()}]: ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (text) => {
|
||||||
|
console.error(`[E] [${Date()}]: ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const warn = (text) => {
|
||||||
|
console.warn(`[W] [${Date()}]: ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const unknownError = (text) => {
|
||||||
|
error(text);
|
||||||
|
return "Unknown server error. Please, report to the developer"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { debug, info, error, warn, unknownError }
|
|
@ -0,0 +1,7 @@
|
||||||
|
const statuses = {
|
||||||
|
ok: "ok",
|
||||||
|
duplicate: "duplicate",
|
||||||
|
not_found: "not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default statuses
|
Loading…
Reference in New Issue