Added users support, made CRUD for users. Updated receipts feature
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.5.2 Chrome/142.0.7444.265 Electron/39.6.1 Safari/537.36" version="29.5.2">
|
||||
<diagram name="Страница-1" id="0m6B3G-Z3EdFeOiLiUiD">
|
||||
<mxGraphModel dx="1357" dy="1036" 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="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-27" parent="1" style="shape=table;startSize=30;container=1;collapsible=0;childLayout=tableLayout;strokeColor=default;fontSize=16;" value="Users" vertex="1">
|
||||
<mxGeometry height="238" width="320" x="40" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-28" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-29" parent="JbCyL7nHgy0AnRVCQc6m-28" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="ID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-30" parent="JbCyL7nHgy0AnRVCQc6m-28" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigSerial" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-31" parent="JbCyL7nHgy0AnRVCQc6m-28" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="PK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-32" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="56" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-33" parent="JbCyL7nHgy0AnRVCQc6m-32" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="TelegramID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-34" parent="JbCyL7nHgy0AnRVCQc6m-32" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigInt" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-35" parent="JbCyL7nHgy0AnRVCQc6m-32" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-36" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="82" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-37" parent="JbCyL7nHgy0AnRVCQc6m-36" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UserName" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-38" parent="JbCyL7nHgy0AnRVCQc6m-36" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-39" parent="JbCyL7nHgy0AnRVCQc6m-36" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-40" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="108" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-41" parent="JbCyL7nHgy0AnRVCQc6m-40" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="FirstName" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-42" parent="JbCyL7nHgy0AnRVCQc6m-40" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-43" parent="JbCyL7nHgy0AnRVCQc6m-40" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-44" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="134" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-45" parent="JbCyL7nHgy0AnRVCQc6m-44" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="LastName" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-46" parent="JbCyL7nHgy0AnRVCQc6m-44" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-47" parent="JbCyL7nHgy0AnRVCQc6m-44" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-57" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="160" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-58" parent="JbCyL7nHgy0AnRVCQc6m-57" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="LanguageCode" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-59" parent="JbCyL7nHgy0AnRVCQc6m-57" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-60" parent="JbCyL7nHgy0AnRVCQc6m-57" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-53" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="186" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-54" parent="JbCyL7nHgy0AnRVCQc6m-53" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="CreatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-55" parent="JbCyL7nHgy0AnRVCQc6m-53" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-56" parent="JbCyL7nHgy0AnRVCQc6m-53" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-49" parent="JbCyL7nHgy0AnRVCQc6m-27" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="212" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-50" parent="JbCyL7nHgy0AnRVCQc6m-49" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UpdatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-51" parent="JbCyL7nHgy0AnRVCQc6m-49" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-52" parent="JbCyL7nHgy0AnRVCQc6m-49" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-61" parent="1" style="shape=table;startSize=30;container=1;collapsible=0;childLayout=tableLayout;strokeColor=default;fontSize=16;gradientColor=none;fillColor=default;" value="Families" vertex="1">
|
||||
<mxGeometry height="238" width="320" x="480" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-62" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-63" parent="JbCyL7nHgy0AnRVCQc6m-62" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="ID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-64" parent="JbCyL7nHgy0AnRVCQc6m-62" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigSerial" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-65" parent="JbCyL7nHgy0AnRVCQc6m-62" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="PK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-152" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="56" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-153" parent="JbCyL7nHgy0AnRVCQc6m-152" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="OwnerId" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-154" parent="JbCyL7nHgy0AnRVCQc6m-152" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigInt" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-155" parent="JbCyL7nHgy0AnRVCQc6m-152" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-66" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="82" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-67" parent="JbCyL7nHgy0AnRVCQc6m-66" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="TelegramChatID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-68" parent="JbCyL7nHgy0AnRVCQc6m-66" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigInt" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-69" parent="JbCyL7nHgy0AnRVCQc6m-66" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-148" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="108" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-149" parent="JbCyL7nHgy0AnRVCQc6m-148" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="TelegramChatName" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-150" parent="JbCyL7nHgy0AnRVCQc6m-148" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-151" parent="JbCyL7nHgy0AnRVCQc6m-148" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-144" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="134" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-145" parent="JbCyL7nHgy0AnRVCQc6m-144" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-146" parent="JbCyL7nHgy0AnRVCQc6m-144" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-147" parent="JbCyL7nHgy0AnRVCQc6m-144" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-140" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="160" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-141" parent="JbCyL7nHgy0AnRVCQc6m-140" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-142" parent="JbCyL7nHgy0AnRVCQc6m-140" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-143" parent="JbCyL7nHgy0AnRVCQc6m-140" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-86" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="186" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-87" parent="JbCyL7nHgy0AnRVCQc6m-86" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="CreatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-88" parent="JbCyL7nHgy0AnRVCQc6m-86" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-89" parent="JbCyL7nHgy0AnRVCQc6m-86" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-90" parent="JbCyL7nHgy0AnRVCQc6m-61" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="212" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-91" parent="JbCyL7nHgy0AnRVCQc6m-90" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UpdatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-92" parent="JbCyL7nHgy0AnRVCQc6m-90" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-93" parent="JbCyL7nHgy0AnRVCQc6m-90" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-95" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="440" y="135" />
|
||||
<mxPoint x="440" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-96" parent="1" style="shape=table;startSize=30;container=1;collapsible=0;childLayout=tableLayout;strokeColor=default;fontSize=16;gradientColor=none;fillColor=default;" value="FamilyMembers" vertex="1">
|
||||
<mxGeometry height="160" width="320" x="480" y="360" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-97" parent="JbCyL7nHgy0AnRVCQc6m-96" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-98" parent="JbCyL7nHgy0AnRVCQc6m-97" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="ID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-99" parent="JbCyL7nHgy0AnRVCQc6m-97" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigSerial" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-100" parent="JbCyL7nHgy0AnRVCQc6m-97" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="PK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-101" parent="JbCyL7nHgy0AnRVCQc6m-96" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="56" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-102" parent="JbCyL7nHgy0AnRVCQc6m-101" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="FamilyId" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-103" parent="JbCyL7nHgy0AnRVCQc6m-101" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigInt" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-104" parent="JbCyL7nHgy0AnRVCQc6m-101" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-105" parent="JbCyL7nHgy0AnRVCQc6m-96" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="82" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-106" parent="JbCyL7nHgy0AnRVCQc6m-105" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UserId" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-107" parent="JbCyL7nHgy0AnRVCQc6m-105" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="Bigint" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-108" parent="JbCyL7nHgy0AnRVCQc6m-105" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-109" parent="JbCyL7nHgy0AnRVCQc6m-96" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="108" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-110" parent="JbCyL7nHgy0AnRVCQc6m-109" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="JoinedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-111" parent="JbCyL7nHgy0AnRVCQc6m-109" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-112" parent="JbCyL7nHgy0AnRVCQc6m-109" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-136" parent="JbCyL7nHgy0AnRVCQc6m-96" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="134" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-137" parent="JbCyL7nHgy0AnRVCQc6m-136" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="Role" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-138" parent="JbCyL7nHgy0AnRVCQc6m-136" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-139" parent="JbCyL7nHgy0AnRVCQc6m-136" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-117" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-101" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-62">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="840" y="429" />
|
||||
<mxPoint x="840" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-118" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-105" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="380" y="455" />
|
||||
<mxPoint x="380" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-119" parent="1" style="shape=table;startSize=30;container=1;collapsible=0;childLayout=tableLayout;strokeColor=default;fontSize=16;gradientColor=none;fillColor=default;" value="FamilyThreads" vertex="1">
|
||||
<mxGeometry height="264" width="320" x="920" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-120" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-121" parent="JbCyL7nHgy0AnRVCQc6m-120" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="ID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-122" parent="JbCyL7nHgy0AnRVCQc6m-120" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigSerial" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-123" parent="JbCyL7nHgy0AnRVCQc6m-120" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="PK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-124" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="56" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-125" parent="JbCyL7nHgy0AnRVCQc6m-124" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="FamilyId" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-126" parent="JbCyL7nHgy0AnRVCQc6m-124" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="BigInt" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-127" parent="JbCyL7nHgy0AnRVCQc6m-124" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-128" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="82" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-129" parent="JbCyL7nHgy0AnRVCQc6m-128" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="TelegramTopicID" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-130" parent="JbCyL7nHgy0AnRVCQc6m-128" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="Bigint" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-131" parent="JbCyL7nHgy0AnRVCQc6m-128" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-171" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="108" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-172" parent="JbCyL7nHgy0AnRVCQc6m-171" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="Title" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-173" parent="JbCyL7nHgy0AnRVCQc6m-171" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-174" parent="JbCyL7nHgy0AnRVCQc6m-171" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-167" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="134" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-168" parent="JbCyL7nHgy0AnRVCQc6m-167" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="Type" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-169" parent="JbCyL7nHgy0AnRVCQc6m-167" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="String" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-170" parent="JbCyL7nHgy0AnRVCQc6m-167" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-132" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="160" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-133" parent="JbCyL7nHgy0AnRVCQc6m-132" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="CreatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-134" parent="JbCyL7nHgy0AnRVCQc6m-132" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-135" parent="JbCyL7nHgy0AnRVCQc6m-132" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-175" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="186" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-176" parent="JbCyL7nHgy0AnRVCQc6m-175" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="CreatedBy" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-177" parent="JbCyL7nHgy0AnRVCQc6m-175" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="Bigint" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-178" parent="JbCyL7nHgy0AnRVCQc6m-175" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-183" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="212" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-184" parent="JbCyL7nHgy0AnRVCQc6m-183" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UpdatedAt" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-185" parent="JbCyL7nHgy0AnRVCQc6m-183" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="TimeStamp" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-186" parent="JbCyL7nHgy0AnRVCQc6m-183" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-179" parent="JbCyL7nHgy0AnRVCQc6m-119" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1">
|
||||
<mxGeometry height="26" width="320" y="238" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-180" parent="JbCyL7nHgy0AnRVCQc6m-179" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;align=left;" value="UpdatedBy" vertex="1">
|
||||
<mxGeometry height="26" width="160" as="geometry">
|
||||
<mxRectangle height="26" width="160" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-181" parent="JbCyL7nHgy0AnRVCQc6m-179" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="Bigint" vertex="1">
|
||||
<mxGeometry height="26" width="120" x="160" as="geometry">
|
||||
<mxRectangle height="26" width="120" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-182" parent="JbCyL7nHgy0AnRVCQc6m-179" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=16;" value="FK" vertex="1">
|
||||
<mxGeometry height="26" width="40" x="280" as="geometry">
|
||||
<mxRectangle height="26" width="40" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-162" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-124" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-62">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="880" y="109" />
|
||||
<mxPoint x="880" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-187" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-175" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="880" y="239" />
|
||||
<mxPoint x="880" y="300" />
|
||||
<mxPoint x="400" y="300" />
|
||||
<mxPoint x="400" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-188" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-179" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="900" y="291" />
|
||||
<mxPoint x="900" y="320" />
|
||||
<mxPoint x="390" y="320" />
|
||||
<mxPoint x="390" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JbCyL7nHgy0AnRVCQc6m-189" edge="1" parent="1" source="JbCyL7nHgy0AnRVCQc6m-152" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="JbCyL7nHgy0AnRVCQc6m-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="410" y="109" />
|
||||
<mxPoint x="410" y="83" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -21,16 +21,24 @@ require (
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go/vision/v2 v2.9.5 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
@@ -39,9 +47,11 @@ require (
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -49,8 +59,14 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/swaggo/gin-swagger v1.6.1 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
@@ -59,17 +75,21 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/api v0.247.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE families
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
owner_id BIGINT NOT NULL REFERENCES users (id),
|
||||
telegram_chat_id BIGINT NOT NULL,
|
||||
telegram_chat_name TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_families_owner_id ON families (owner_id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_families_chat_id ON families (telegram_chat_id);
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE IF EXISTS telegram_chats;
|
||||
@@ -1,9 +0,0 @@
|
||||
CREATE TABLE telegram_chats
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
telegram_id BIGINT UNIQUE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_telegram_chats_telegram_id ON telegram_chats (telegram_id);
|
||||
@@ -1,12 +0,0 @@
|
||||
CREATE TABLE families
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
owner_id BIGINT NOT NULL REFERENCES users (id),
|
||||
telegram_chat_id BIGINT NOT NULL UNIQUE REFERENCES telegram_chats (id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_families_owner_id ON families (owner_id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_families_chat_id ON families (telegram_chat_id);
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE family_threads
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
family_id BIGINT NOT NULL REFERENCES families (id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
telegram_topic_id BIGINT NOT NULL,
|
||||
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_by BIGINT NOT NULL REFERENCES users (id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE (family_id, telegram_topic_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_system_threads ON family_threads (family_id, type) WHERE is_system = TRUE;
|
||||
|
||||
CREATE INDEX idx_threads_family_id ON family_threads (family_id);
|
||||
|
||||
CREATE INDEX idx_threads_family_type ON family_threads (family_id, type);
|
||||
@@ -1,27 +0,0 @@
|
||||
CREATE TYPE thread_type AS ENUM (
|
||||
'expenses',
|
||||
'movies',
|
||||
'schedule',
|
||||
'recipes',
|
||||
'custom'
|
||||
);
|
||||
|
||||
CREATE TABLE threads
|
||||
(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
family_id BIGINT NOT NULL REFERENCES families (id) ON DELETE CASCADE,
|
||||
type thread_type NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
telegram_topic_id BIGINT NOT NULL,
|
||||
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_by BIGINT NOT NULL REFERENCES users (id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE (family_id, telegram_topic_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_system_threads ON threads (family_id, type) WHERE is_system = TRUE;
|
||||
|
||||
CREATE INDEX idx_threads_family_id ON threads (family_id);
|
||||
|
||||
CREATE INDEX idx_threads_family_type ON threads(family_id, type);
|
||||
@@ -0,0 +1,401 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/users": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Создать пользователя",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.CreateUserRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.UserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/by-telegram/{telegramId}": {
|
||||
"get": {
|
||||
"description": "Возвращает пользователя по его Telegram ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Получить пользователя по Telegram ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Telegram ID",
|
||||
"name": "telegramId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.UserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid telegram id",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "user not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{id}": {
|
||||
"get": {
|
||||
"description": "Возвращает пользователя по его внутреннему ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Получить пользователя по ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.UserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "user not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Удаляет пользователя по его ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Удалить пользователя",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "no content",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "user not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"description": "Частично обновляет данные пользователя по ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Обновить пользователя",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Данные для обновления",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.UpdateUserRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.UserResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id or invalid body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "user not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"dto.CreateUserRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"first_name",
|
||||
"telegram_id"
|
||||
],
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"language_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"telegram_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.UpdateUserRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"language_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.UserResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"language_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"telegram_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dto
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package dto
|
||||
|
||||
type HelloRequest struct {
|
||||
Name string `form:"name" binding:"required,min=2,max=50"`
|
||||
}
|
||||
import "time"
|
||||
|
||||
type AddReceiptRequest struct {
|
||||
Number string `json:"number" binding:"required,min=24,max=24"`
|
||||
Date string `json:"date" binding:"required"`
|
||||
}
|
||||
|
||||
type AddReceiptResponse struct {
|
||||
ID int32 `json:"id"`
|
||||
Number string `json:"number"`
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type HelloResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AddReceiptResponse struct {
|
||||
ID int32 `json:"id"`
|
||||
Number string `json:"number"`
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"FamilyHub/src/domain"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreateUserRequest struct {
|
||||
TelegramID int64 `json:"telegram_id" validate:"required"`
|
||||
Username *string `json:"username"`
|
||||
FirstName string `json:"first_name" validate:"required"`
|
||||
LastName *string `json:"last_name"`
|
||||
LanguageCode *string `json:"language_code"`
|
||||
}
|
||||
type UpdateUserRequest struct {
|
||||
Username *string `json:"username"`
|
||||
FirstName *string `json:"first_name"`
|
||||
LastName *string `json:"last_name"`
|
||||
LanguageCode *string `json:"language_code"`
|
||||
}
|
||||
type UserResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
TelegramID int64 `json:"telegram_id"`
|
||||
Username *string `json:"username"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName *string `json:"last_name"`
|
||||
LanguageCode *string `json:"language_code"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (response *UserResponse) ModelToResponse(u *domain.User) UserResponse {
|
||||
return UserResponse{
|
||||
ID: u.ID,
|
||||
TelegramID: u.TelegramID,
|
||||
Username: u.Username,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
LanguageCode: u.LanguageCode,
|
||||
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers
|
||||
package routers
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api/dto"
|
||||
@@ -12,15 +12,21 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ReceiptHandler struct {
|
||||
type ReceiptRouter struct {
|
||||
service *receiptService.ReceiptService
|
||||
}
|
||||
|
||||
func NewReceiptHandler(s *receiptService.ReceiptService) *ReceiptHandler {
|
||||
return &ReceiptHandler{service: s}
|
||||
func NewReceiptRouter(s *receiptService.ReceiptService) *ReceiptRouter {
|
||||
return &ReceiptRouter{service: s}
|
||||
}
|
||||
func (router *ReceiptRouter) RegisterRoutes(r *gin.RouterGroup) {
|
||||
receipts := r.Group("/receipts")
|
||||
{
|
||||
receipts.POST("", router.AddReceipt)
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *ReceiptHandler) AddReceipt(context_ *gin.Context) {
|
||||
func (router *ReceiptRouter) AddReceipt(context_ *gin.Context) {
|
||||
var req dto.AddReceiptRequest
|
||||
if err := context_.ShouldBindJSON(&req); err != nil {
|
||||
log.Println("bind error:", err)
|
||||
@@ -36,7 +42,7 @@ func (handler *ReceiptHandler) AddReceipt(context_ *gin.Context) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
receipt, err := handler.service.GetReceipt(ctx, isoDate, req.Number)
|
||||
receipt, err := router.service.GetReceipt(ctx, isoDate, req.Number)
|
||||
if err != nil {
|
||||
context_.JSON(400, gin.H{"error": err.Error()})
|
||||
log.Printf("API error, %s", err.Error())
|
||||
@@ -0,0 +1,192 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api/dto"
|
||||
"FamilyHub/src/api/services"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UsersRouter struct {
|
||||
service services.UserService
|
||||
}
|
||||
|
||||
func NewUsersRouter(s *services.UserService) *UsersRouter {
|
||||
return &UsersRouter{service: *s}
|
||||
}
|
||||
|
||||
func (router *UsersRouter) RegisterRoutes(r *gin.RouterGroup) {
|
||||
users := r.Group("/users")
|
||||
{
|
||||
users.POST("", router.CreateUser)
|
||||
users.GET("/:id", router.GetByID)
|
||||
users.GET("/by-telegram/:telegramId", router.GetByTelegramID)
|
||||
users.PATCH("/:id", router.Update)
|
||||
users.DELETE("/:id", router.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser GoDoc
|
||||
// @Summary Создать пользователя
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user body dto.CreateUserRequest true "User info"
|
||||
// @Success 201 {object} dto.UserResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /users [post]
|
||||
func (router *UsersRouter) CreateUser(c *gin.Context) {
|
||||
var req dto.CreateUserRequest
|
||||
var resp dto.UserResponse
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := router.service.Create(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, resp.ModelToResponse(user))
|
||||
}
|
||||
|
||||
// GetByID GoDoc
|
||||
// @Summary Получить пользователя по ID
|
||||
// @Description Возвращает пользователя по его внутреннему ID
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 200 {object} dto.UserResponse
|
||||
// @Failure 400 {object} map[string]string "invalid id"
|
||||
// @Failure 404 {object} map[string]string "user not found"
|
||||
// @Failure 500 {object} map[string]string "internal server error"
|
||||
// @Router /users/{id} [get]
|
||||
func (router *UsersRouter) GetByID(c *gin.Context) {
|
||||
var resp dto.UserResponse
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := router.service.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp.ModelToResponse(user))
|
||||
}
|
||||
|
||||
// GetByTelegramID GoDoc
|
||||
// @Summary Получить пользователя по Telegram ID
|
||||
// @Description Возвращает пользователя по его Telegram ID
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param telegramId path int true "Telegram ID"
|
||||
// @Success 200 {object} dto.UserResponse
|
||||
// @Failure 400 {object} map[string]string "invalid telegram id"
|
||||
// @Failure 404 {object} map[string]string "user not found"
|
||||
// @Failure 500 {object} map[string]string "internal server error"
|
||||
// @Router /users/by-telegram/{telegramId} [get]
|
||||
func (router *UsersRouter) GetByTelegramID(c *gin.Context) {
|
||||
var resp dto.UserResponse
|
||||
telegramID, err := strconv.ParseInt(c.Param("telegramId"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid telegram id"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := router.service.GetByTelegramID(c.Request.Context(), telegramID)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp.ModelToResponse(user))
|
||||
}
|
||||
|
||||
// Update GoDoc
|
||||
// @Summary Обновить пользователя
|
||||
// @Description Частично обновляет данные пользователя по ID
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Param user body dto.UpdateUserRequest true "Данные для обновления"
|
||||
// @Success 200 {object} dto.UserResponse
|
||||
// @Failure 400 {object} map[string]string "invalid id or invalid body"
|
||||
// @Failure 404 {object} map[string]string "user not found"
|
||||
// @Failure 500 {object} map[string]string "internal server error"
|
||||
// @Router /users/{id} [patch]
|
||||
func (router *UsersRouter) Update(c *gin.Context) {
|
||||
var resp dto.UserResponse
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.UpdateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := router.service.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp.ModelToResponse(user))
|
||||
}
|
||||
|
||||
// Delete GoDoc
|
||||
// @Summary Удалить пользователя
|
||||
// @Description Удаляет пользователя по его ID
|
||||
// @Tags Users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "User ID"
|
||||
// @Success 204 {string} string "no content"
|
||||
// @Failure 400 {object} map[string]string "invalid id"
|
||||
// @Failure 404 {object} map[string]string "user not found"
|
||||
// @Failure 500 {object} map[string]string "internal server error"
|
||||
// @Router /users/{id} [delete]
|
||||
func (router *UsersRouter) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := router.service.Delete(c.Request.Context(), id); err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func handleError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, services.ErrUserNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, services.ErrInvalidPatch):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, services.ErrTelegramIDMissing):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
||||
}
|
||||
}
|
||||
+20
-10
@@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api/handlers"
|
||||
_ "FamilyHub/src/api/docs"
|
||||
"FamilyHub/src/api/routers"
|
||||
"FamilyHub/src/api/services"
|
||||
"FamilyHub/src/config"
|
||||
"FamilyHub/src/database"
|
||||
"FamilyHub/src/integrations/receiptService"
|
||||
@@ -11,6 +13,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -18,7 +22,6 @@ type Server struct {
|
||||
}
|
||||
|
||||
func NewServer(cfg config.Config) *Server {
|
||||
handler := gin.Default()
|
||||
dbManager := &database.Database{
|
||||
ConnectionString: cfg.DBConnectionString,
|
||||
MigrationsPath: "file://migrations",
|
||||
@@ -27,24 +30,31 @@ func NewServer(cfg config.Config) *Server {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := dbManager.RunMigrations(dbConn); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
receiptRepo := repositories.NewReceiptSQLRepository(dbConn)
|
||||
receiptService_ := receiptService.NewReceiptService(receiptRepo)
|
||||
receiptHandler := handlers.NewReceiptHandler(receiptService_)
|
||||
|
||||
router := gin.Default()
|
||||
if cfg.OpenAPIEnabled {
|
||||
handler.GET(cfg.OpenAPIEndpoint)
|
||||
router.GET("/openapi/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
handler.POST("/receipts", receiptHandler.AddReceipt)
|
||||
|
||||
apiV1 := router.Group("/api/v1")
|
||||
|
||||
receiptRepo := repositories.NewReceiptsSQLRepository(dbConn)
|
||||
receiptService_ := receiptService.NewReceiptService(receiptRepo)
|
||||
receiptRouter := routers.NewReceiptRouter(receiptService_)
|
||||
receiptRouter.RegisterRoutes(apiV1)
|
||||
|
||||
usersRepo := repositories.NewUsersSQLRepository(dbConn)
|
||||
usersService := services.NewUserService(usersRepo)
|
||||
usersRouter := routers.NewUsersRouter(&usersService)
|
||||
usersRouter.RegisterRoutes(apiV1)
|
||||
|
||||
return &Server{
|
||||
httpServer: &http.Server{
|
||||
Addr: cfg.APIHost + ":" + cfg.APIPort,
|
||||
Handler: handler,
|
||||
Handler: router,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api/dto"
|
||||
"FamilyHub/src/domain"
|
||||
"FamilyHub/src/repositories"
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type UserService interface {
|
||||
Create(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error)
|
||||
GetByID(ctx context.Context, id int64) (*domain.User, error)
|
||||
GetByTelegramID(ctx context.Context, telegramID int64) (*domain.User, error)
|
||||
Update(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error)
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
repo repositories.UsersRepository
|
||||
}
|
||||
|
||||
func NewUserService(repo repositories.UsersRepository) UserService {
|
||||
return &userService{repo: repo}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrInvalidPatch = errors.New("empty update payload")
|
||||
ErrTelegramIDMissing = errors.New("telegram_id is required")
|
||||
)
|
||||
|
||||
func (s *userService) Create(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error) {
|
||||
user_ := &domain.User{
|
||||
TelegramID: req.TelegramID,
|
||||
Username: req.Username,
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
LanguageCode: req.LanguageCode,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, user_); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user_, nil
|
||||
}
|
||||
func (s *userService) GetByID(ctx context.Context, id int64) (*domain.User, error) {
|
||||
user, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
func (s *userService) GetByTelegramID(ctx context.Context, telegramID int64) (*domain.User, error) {
|
||||
user, err := s.repo.GetByTelegramID(ctx, telegramID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
func (s *userService) Update(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error) {
|
||||
existing, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
if req.Username == nil &&
|
||||
req.FirstName == nil &&
|
||||
req.LastName == nil &&
|
||||
req.LanguageCode == nil {
|
||||
return nil, ErrInvalidPatch
|
||||
}
|
||||
|
||||
if err := s.repo.Update(ctx, &domain.User{
|
||||
ID: id,
|
||||
Username: req.Username,
|
||||
FirstName: *req.FirstName,
|
||||
LastName: req.LastName,
|
||||
LanguageCode: req.LanguageCode,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.repo.GetByID(ctx, id)
|
||||
}
|
||||
func (s *userService) Delete(ctx context.Context, id int64) error {
|
||||
user, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
return s.repo.Delete(ctx, id)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"GoFinanceManager/internal/config"
|
||||
"FamilyHub/src/config"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package models
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
TelegramID int64
|
||||
Username *string
|
||||
FirstName string
|
||||
LastName *string
|
||||
LanguageCode *string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package receiptService
|
||||
|
||||
import (
|
||||
"FamilyHub/src/domain/models"
|
||||
"FamilyHub/src/domain"
|
||||
"FamilyHub/src/repositories"
|
||||
"FamilyHub/src/utils"
|
||||
"bytes"
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
|
||||
type ReceiptService struct {
|
||||
client *http.Client
|
||||
repo repositories.ReceiptRepository
|
||||
repo repositories.ReceiptsRepository
|
||||
}
|
||||
|
||||
func NewReceiptService(repo repositories.ReceiptRepository) *ReceiptService {
|
||||
func NewReceiptService(repo repositories.ReceiptsRepository) *ReceiptService {
|
||||
return &ReceiptService{
|
||||
client: &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
@@ -39,9 +39,9 @@ func (s *ReceiptService) GetReceipt(
|
||||
ctx context.Context,
|
||||
date string,
|
||||
number string,
|
||||
) (*models.Receipt, error) {
|
||||
) (*domain.Receipt, error) {
|
||||
url := "https://ch.info-center.by/ajax/check1.php"
|
||||
var receipt models.Receipt
|
||||
var receipt domain.Receipt
|
||||
|
||||
body, contentType := buildMultipartBody(date, number)
|
||||
req, err := http.NewRequestWithContext(
|
||||
@@ -137,8 +137,8 @@ func buildMultipartBody(date, number string) (*bytes.Buffer, string) {
|
||||
return body, writer.FormDataContentType()
|
||||
}
|
||||
|
||||
func parsePositions(raw string) ([]models.Position, error) {
|
||||
var positions []models.Position
|
||||
func parsePositions(raw string) ([]domain.Position, error) {
|
||||
var positions []domain.Position
|
||||
|
||||
if raw == "" {
|
||||
return positions, nil
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"FamilyHub/src/domain/models"
|
||||
)
|
||||
|
||||
type ReceiptRepository interface {
|
||||
Create(ctx context.Context, receipt *models.Receipt) (int64, error)
|
||||
GetByID(ctx context.Context, id int64) (*models.Receipt, error)
|
||||
GetAll(ctx context.Context, limit, offset int) ([]*models.Receipt, error)
|
||||
Update(ctx context.Context, receipt *models.Receipt) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
@@ -5,18 +5,26 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"FamilyHub/src/domain/models"
|
||||
"FamilyHub/src/domain"
|
||||
)
|
||||
|
||||
type ReceiptSQLRepository struct {
|
||||
type ReceiptsRepository interface {
|
||||
Create(ctx context.Context, receipt *domain.Receipt) (int64, error)
|
||||
GetByID(ctx context.Context, id int64) (*domain.Receipt, error)
|
||||
GetAll(ctx context.Context, limit, offset int) ([]*domain.Receipt, error)
|
||||
Update(ctx context.Context, receipt *domain.Receipt) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type ReceiptsSQLRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewReceiptSQLRepository(db *sql.DB) *ReceiptSQLRepository {
|
||||
return &ReceiptSQLRepository{db: db}
|
||||
func NewReceiptsSQLRepository(db *sql.DB) *ReceiptsSQLRepository {
|
||||
return &ReceiptsSQLRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Create(ctx context.Context, receipt *models.Receipt) (int64, error) {
|
||||
func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Receipt) (int64, error) {
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
@@ -129,9 +137,9 @@ func (r *ReceiptSQLRepository) Create(ctx context.Context, receipt *models.Recei
|
||||
return receiptID, tx.Commit()
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) GetByID(ctx context.Context, id int64) (*models.Receipt, error) {
|
||||
func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.Receipt, error) {
|
||||
|
||||
var receipt models.Receipt
|
||||
var receipt domain.Receipt
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT
|
||||
@@ -145,7 +153,7 @@ func (r *ReceiptSQLRepository) GetByID(ctx context.Context, id int64) (*models.R
|
||||
street_to, house_to,
|
||||
kod_soato, oblast_soato, rayon_soato, selsovet_soato,
|
||||
doc_num, skno_number, unp,
|
||||
success, raw_json
|
||||
success
|
||||
FROM receipts
|
||||
WHERE id = ?
|
||||
`, id).Scan(
|
||||
@@ -209,7 +217,7 @@ func (r *ReceiptSQLRepository) GetByID(ctx context.Context, id int64) (*models.R
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var p models.Position
|
||||
var p domain.Position
|
||||
if err := rows.Scan(
|
||||
&p.SectionNumber,
|
||||
&p.GTINCode,
|
||||
@@ -230,7 +238,7 @@ func (r *ReceiptSQLRepository) GetByID(ctx context.Context, id int64) (*models.R
|
||||
return &receipt, nil
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*models.Receipt, error) {
|
||||
func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*domain.Receipt, error) {
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, receipt_number, issued_at, total_amount, currency
|
||||
@@ -243,10 +251,10 @@ func (r *ReceiptSQLRepository) GetAll(ctx context.Context, limit, offset int) ([
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var receipts []*models.Receipt
|
||||
var receipts []*domain.Receipt
|
||||
|
||||
for rows.Next() {
|
||||
var rct models.Receipt
|
||||
var rct domain.Receipt
|
||||
if err := rows.Scan(
|
||||
&rct.ID,
|
||||
&rct.ReceiptNumber,
|
||||
@@ -262,7 +270,7 @@ func (r *ReceiptSQLRepository) GetAll(ctx context.Context, limit, offset int) ([
|
||||
return receipts, nil
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Update(ctx context.Context, receipt *models.Receipt) error {
|
||||
func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Receipt) error {
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
@@ -305,7 +313,7 @@ func (r *ReceiptSQLRepository) Update(ctx context.Context, receipt *models.Recei
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *ReceiptSQLRepository) Delete(ctx context.Context, id int64) error {
|
||||
func (r *ReceiptsSQLRepository) Delete(ctx context.Context, id int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
`DELETE FROM receipts WHERE id = ?`,
|
||||
id,
|
||||
@@ -0,0 +1,158 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"FamilyHub/src/domain"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type UsersRepository interface {
|
||||
Create(ctx context.Context, user *domain.User) error
|
||||
GetByID(ctx context.Context, id int64) (*domain.User, error)
|
||||
GetByTelegramID(ctx context.Context, telegramID int64) (*domain.User, error)
|
||||
Update(ctx context.Context, user *domain.User) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type UsersSQLRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewUsersSQLRepository(db *sql.DB) *UsersSQLRepository {
|
||||
return &UsersSQLRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *UsersSQLRepository) Create(ctx context.Context, user *domain.User) error {
|
||||
query := `
|
||||
INSERT INTO users
|
||||
(telegram_id, username, first_name, last_name, language_code)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
return r.db.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
user.TelegramID,
|
||||
user.Username,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.LanguageCode,
|
||||
).Scan(&user.ID, &user.CreatedAt, &user.UpdatedAt)
|
||||
}
|
||||
func (r *UsersSQLRepository) GetByID(ctx context.Context, id int64) (*domain.User, error) {
|
||||
query := `
|
||||
SELECT
|
||||
id,
|
||||
telegram_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name,
|
||||
language_code,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var user domain.User
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||
&user.ID,
|
||||
&user.TelegramID,
|
||||
&user.Username,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.LanguageCode,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil // или кастомную ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
func (r *UsersSQLRepository) GetByTelegramID(ctx context.Context, telegramID int64) (*domain.User, error) {
|
||||
query := `
|
||||
SELECT
|
||||
id,
|
||||
telegram_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name,
|
||||
language_code,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM users
|
||||
WHERE telegram_id = $1
|
||||
`
|
||||
|
||||
var user domain.User
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, telegramID).Scan(
|
||||
&user.ID,
|
||||
&user.TelegramID,
|
||||
&user.Username,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.LanguageCode,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
func (r *UsersSQLRepository) Update(ctx context.Context, user *domain.User) error {
|
||||
query := `
|
||||
UPDATE users SET
|
||||
username = $1,
|
||||
first_name = $2,
|
||||
last_name = $3,
|
||||
language_code = $4,
|
||||
updated_at = now()
|
||||
WHERE id = $5
|
||||
RETURNING updated_at
|
||||
`
|
||||
|
||||
return r.db.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
user.Username,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.LanguageCode,
|
||||
user.ID,
|
||||
).Scan(&user.UpdatedAt)
|
||||
}
|
||||
func (r *UsersSQLRepository) Delete(ctx context.Context, id int64) error {
|
||||
query := `DELETE FROM users WHERE id = $1`
|
||||
|
||||
result, err := r.db.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rows == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user