forked from Ruderverein-Donau-Linz/rowt
parent
0cc72f17a1
commit
a6a143f238
@ -9,6 +9,7 @@ Thus, here is the current (October '24) model and the reasoning behind it:
|
|||||||
- All user-relevant fields are stored in `User`.
|
- All user-relevant fields are stored in `User`.
|
||||||
- `Role` (and its associative table `UserRole`) map current roles the user has. This is used for e.g. permissions (`Vorstand`, `Admin`, `cox`, ... roles) and fee calculation (`Donau Linz`, `scheckbuch`, `Rennjugend`).
|
- `Role` (and its associative table `UserRole`) map current roles the user has. This is used for e.g. permissions (`Vorstand`, `Admin`, `cox`, ... roles) and fee calculation (`Donau Linz`, `scheckbuch`, `Rennjugend`).
|
||||||
- `Family` specifies, well, a family. Currently only used for fee calculation.
|
- `Family` specifies, well, a family. Currently only used for fee calculation.
|
||||||
|
- `cluster` in `Role` groups roles together. There is a db check to only allow for at most 1 role of the same cluster (e.g. either `cox` or `bootsfuehrer`, but not both).
|
||||||
|
|
||||||
## Planned rowing adventures :-)
|
## Planned rowing adventures :-)
|
||||||
![](./planned.svg)
|
![](./planned.svg)
|
||||||
|
@ -29,6 +29,7 @@ classDiagram
|
|||||||
class Role {
|
class Role {
|
||||||
+int id
|
+int id
|
||||||
+string name
|
+string name
|
||||||
|
+string cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserRole {
|
class UserRole {
|
||||||
|
@ -1 +1 @@
|
|||||||
<svg aria-roledescription="classDiagram" role="graphics-document document" viewBox="0 0 398.859375 664" style="max-width: 398.859px; background-color: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#ccc;}#my-svg .error-icon{fill:#a44141;}#my-svg .error-text{fill:#ddd;stroke:#ddd;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:lightgrey;stroke:lightgrey;}#my-svg .marker.cross{stroke:lightgrey;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg g.classGroup text{fill:#ccc;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#my-svg g.classGroup text .title{font-weight:bolder;}#my-svg .nodeLabel,#my-svg .edgeLabel{color:#e0dfdf;}#my-svg .edgeLabel .label rect{fill:#1f2020;}#my-svg .label text{fill:#e0dfdf;}#my-svg .edgeLabel .label span{background:#1f2020;}#my-svg .classTitle{font-weight:bolder;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#1f2020;stroke:#ccc;stroke-width:1px;}#my-svg .divider{stroke:#ccc;stroke-width:1;}#my-svg g.clickable{cursor:pointer;}#my-svg g.classGroup rect{fill:#1f2020;stroke:#ccc;}#my-svg g.classGroup line{stroke:#ccc;stroke-width:1;}#my-svg .classLabel .box{stroke:none;stroke-width:0;fill:#1f2020;opacity:0.5;}#my-svg .classLabel .label{fill:#ccc;font-size:10px;}#my-svg .relation{stroke:lightgrey;stroke-width:1;fill:none;}#my-svg .dashed-line{stroke-dasharray:3;}#my-svg .dotted-line{stroke-dasharray:1 2;}#my-svg #compositionStart,#my-svg .composition{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #compositionEnd,#my-svg .composition{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #extensionStart,#my-svg .extension{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #extensionEnd,#my-svg .extension{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #aggregationStart,#my-svg .aggregation{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #aggregationEnd,#my-svg .aggregation{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #lollipopStart,#my-svg .lollipop{fill:#1f2020!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #lollipopEnd,#my-svg .lollipop{fill:#1f2020!important;stroke:lightgrey!important;stroke-width:1;}#my-svg .edgeTerminals{font-size:11px;line-height:initial;}#my-svg .classTitleText{text-anchor:middle;font-size:18px;fill:#ccc;}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker aggregation classDiagram" id="my-svg_classDiagram-aggregationStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker aggregation classDiagram" id="my-svg_classDiagram-aggregationEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker extension classDiagram" id="my-svg_classDiagram-extensionStart"><path d="M 1,7 L18,13 V 1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker extension classDiagram" id="my-svg_classDiagram-extensionEnd"><path d="M 1,1 V 13 L18,7 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker composition classDiagram" id="my-svg_classDiagram-compositionStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker composition classDiagram" id="my-svg_classDiagram-compositionEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="6" class="marker dependency classDiagram" id="my-svg_classDiagram-dependencyStart"><path d="M 5,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="13" class="marker dependency classDiagram" id="my-svg_classDiagram-dependencyEnd"><path d="M 18,7 L9,13 L14,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="13" class="marker lollipop classDiagram" id="my-svg_classDiagram-lollipopStart"><circle r="6" cy="7" cx="7" fill="transparent" stroke="black"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="1" class="marker lollipop classDiagram" id="my-svg_classDiagram-lollipopEnd"><circle r="6" cy="7" cx="7" fill="transparent" stroke="black"/></marker></defs><g class="root"><g class="clusters"/><g class="edgePaths"><path style="fill:none" class="edge-pattern-solid relation" id="id_User_UserRole_1" d="M63.596,505L62.743,509.167C61.891,513.333,60.186,521.667,62.422,530C64.658,538.333,70.835,546.667,73.923,550.833L77.012,555"/><path style="fill:none" class="edge-pattern-solid relation" id="id_Role_UserRole_2" d="M314.526,307L306.921,344.167C299.315,381.333,284.105,455.667,258.939,501.418C233.773,547.168,198.652,564.337,181.092,572.921L163.531,581.505"/><path style="fill:none" class="edge-pattern-solid relation" id="id_User_Family_3" d="M220.891,382.85L241.552,407.375C262.214,431.9,303.536,480.95,324.198,511.475C344.859,542,344.859,554,344.859,560L344.859,566"/></g><g class="edgeLabels"><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(48.863498808878106, 521.5528105004219)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(75.90424420716624, 527.7282047434567)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">*</span></div></foreignObject></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(296.32209487100346, 321.13768236331504)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(180.84087132395615, 582.2957161369667)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">*</span></div></foreignObject></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(220.69417492230585, 405.8979200584628)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(354.8593774999998, 543.5000021428572)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 36px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">0..1</span></div></foreignObject></g></g><g class="nodes"><g transform="translate(114.4453125, 256.5)" id="classId-User-0" class="node default"><rect height="497" width="212.890625" y="-248.5" x="-106.4453125" class="outer title-state" style=""/><line y2="-218.5" y1="-218.5" x2="106.4453125" x1="-106.4453125" class="divider"/><line y2="237.5" y1="237.5" x2="106.4453125" x1="-106.4453125" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -17.7890625, -241)" height="18" width="35.578125" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">User</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -207)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -185)" height="18" width="92.9375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string name</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -163)" height="18" width="73.375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string pw</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -141)" height="18" width="96.53125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+bool deleted</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -119)" height="18" width="158.75"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+datetime last_access</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -97)" height="18" width="79.609375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dob</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -75)" height="18" width="99.171875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string weight</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -53)" height="18" width="77.8125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string sex</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -31)" height="18" width="126.71875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dirty_thirty</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -9)" height="18" width="135.640625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dirty_dozen</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 13)" height="18" width="197.890625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string member_since_date</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 35)" height="18" width="115.1875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string birthdate</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 57)" height="18" width="82.25"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string mail</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 79)" height="18" width="121.390625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string nickname</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 101)" height="18" width="92.0625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string notes</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 123)" height="18" width="97.40625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string phone</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 145)" height="18" width="109.84375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string address</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 167)" height="18" width="93.828125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int family_id</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 189)" height="18" width="163.21875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+blob membership_pdf</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 211)" height="18" width="132.078125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string user_token</span></div></foreignObject></g></g><g transform="translate(344.859375, 605.5)" id="classId-Family-1" class="node default"><rect height="79" width="65.6875" y="-39.5" x="-32.84375" class="outer title-state" style=""/><line y2="-9.5" y1="-9.5" x2="32.84375" x1="-32.84375" class="divider"/><line y2="28.5" y1="28.5" x2="32.84375" x1="-32.84375" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -25.34375, -32)" height="18" width="50.6875" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">Family</span></div></foreignObject><foreignObject transform="translate( -25.34375, 2)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject></g></g><g transform="translate(324.859375, 256.5)" id="classId-Role-2" class="node default"><rect height="101" width="107.9375" y="-50.5" x="-53.96875" class="outer title-state" style=""/><line y2="-20.5" y1="-20.5" x2="53.96875" x1="-53.96875" class="divider"/><line y2="39.5" y1="39.5" x2="53.96875" x1="-53.96875" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -17.3359375, -43)" height="18" width="34.671875" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">Role</span></div></foreignObject><foreignObject transform="translate( -46.46875, -9)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject><foreignObject transform="translate( -46.46875, 13)" height="18" width="92.9375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string name</span></div></foreignObject></g></g><g transform="translate(114.4453125, 605.5)" id="classId-UserRole-3" class="node default"><rect height="101" width="98.171875" y="-50.5" x="-49.0859375" class="outer title-state" style=""/><line y2="-20.5" y1="-20.5" x2="49.0859375" x1="-49.0859375" class="divider"/><line y2="39.5" y1="39.5" x2="49.0859375" x1="-49.0859375" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -35.125, -43)" height="18" width="70.25" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">UserRole</span></div></foreignObject><foreignObject transform="translate( -41.5859375, -9)" height="18" width="83.171875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int user_id</span></div></foreignObject><foreignObject transform="translate( -41.5859375, 13)" height="18" width="78.71875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int role_id</span></div></foreignObject></g></g></g></g></g></svg>
|
<svg aria-roledescription="classDiagram" role="graphics-document document" viewBox="0 0 402.4140625 664" style="max-width: 402.414px; background-color: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#ccc;}#my-svg .error-icon{fill:#a44141;}#my-svg .error-text{fill:#ddd;stroke:#ddd;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:lightgrey;stroke:lightgrey;}#my-svg .marker.cross{stroke:lightgrey;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg g.classGroup text{fill:#ccc;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#my-svg g.classGroup text .title{font-weight:bolder;}#my-svg .nodeLabel,#my-svg .edgeLabel{color:#e0dfdf;}#my-svg .edgeLabel .label rect{fill:#1f2020;}#my-svg .label text{fill:#e0dfdf;}#my-svg .edgeLabel .label span{background:#1f2020;}#my-svg .classTitle{font-weight:bolder;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#1f2020;stroke:#ccc;stroke-width:1px;}#my-svg .divider{stroke:#ccc;stroke-width:1;}#my-svg g.clickable{cursor:pointer;}#my-svg g.classGroup rect{fill:#1f2020;stroke:#ccc;}#my-svg g.classGroup line{stroke:#ccc;stroke-width:1;}#my-svg .classLabel .box{stroke:none;stroke-width:0;fill:#1f2020;opacity:0.5;}#my-svg .classLabel .label{fill:#ccc;font-size:10px;}#my-svg .relation{stroke:lightgrey;stroke-width:1;fill:none;}#my-svg .dashed-line{stroke-dasharray:3;}#my-svg .dotted-line{stroke-dasharray:1 2;}#my-svg #compositionStart,#my-svg .composition{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #compositionEnd,#my-svg .composition{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:lightgrey!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #extensionStart,#my-svg .extension{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #extensionEnd,#my-svg .extension{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #aggregationStart,#my-svg .aggregation{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #aggregationEnd,#my-svg .aggregation{fill:transparent!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #lollipopStart,#my-svg .lollipop{fill:#1f2020!important;stroke:lightgrey!important;stroke-width:1;}#my-svg #lollipopEnd,#my-svg .lollipop{fill:#1f2020!important;stroke:lightgrey!important;stroke-width:1;}#my-svg .edgeTerminals{font-size:11px;line-height:initial;}#my-svg .classTitleText{text-anchor:middle;font-size:18px;fill:#ccc;}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker aggregation classDiagram" id="my-svg_classDiagram-aggregationStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker aggregation classDiagram" id="my-svg_classDiagram-aggregationEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker extension classDiagram" id="my-svg_classDiagram-extensionStart"><path d="M 1,7 L18,13 V 1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker extension classDiagram" id="my-svg_classDiagram-extensionEnd"><path d="M 1,1 V 13 L18,7 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker composition classDiagram" id="my-svg_classDiagram-compositionStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker composition classDiagram" id="my-svg_classDiagram-compositionEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="6" class="marker dependency classDiagram" id="my-svg_classDiagram-dependencyStart"><path d="M 5,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="13" class="marker dependency classDiagram" id="my-svg_classDiagram-dependencyEnd"><path d="M 18,7 L9,13 L14,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="13" class="marker lollipop classDiagram" id="my-svg_classDiagram-lollipopStart"><circle r="6" cy="7" cx="7" fill="transparent" stroke="black"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="1" class="marker lollipop classDiagram" id="my-svg_classDiagram-lollipopEnd"><circle r="6" cy="7" cx="7" fill="transparent" stroke="black"/></marker></defs><g class="root"><g class="clusters"/><g class="edgePaths"><path style="fill:none" class="edge-pattern-solid relation" id="id_User_UserRole_1" d="M63.596,505L62.743,509.167C61.891,513.333,60.186,521.667,62.422,530C64.658,538.333,70.835,546.667,73.923,550.833L77.012,555"/><path style="fill:none" class="edge-pattern-solid relation" id="id_Role_UserRole_2" d="M315.83,318L308.6,353.333C301.37,388.667,286.909,459.333,261.526,503.341C236.143,547.348,199.837,564.697,181.684,573.371L163.531,582.045"/><path style="fill:none" class="edge-pattern-solid relation" id="id_User_Family_3" d="M220.891,380.93L242.145,405.775C263.398,430.62,305.906,480.31,327.16,511.155C348.414,542,348.414,554,348.414,560L348.414,566"/></g><g class="edgeLabels"><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(48.863498808878106, 521.5528105004219)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(75.90424420716624, 527.7282047434567)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">*</span></div></foreignObject></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(297.62591269237316, 332.1376838974091)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(180.78836069993307, 583.034192966888)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">*</span></div></foreignObject></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(220.86826990826685, 403.9791151705619)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"><foreignObject style="width: 9px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">1</span></div></foreignObject></g></g><g transform="translate(358.41406125, 543.4999989285715)" class="edgeTerminals"><g transform="translate(0, 0)" class="inner"/><foreignObject style="width: 36px; height: 12px;"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">0..1</span></div></foreignObject></g></g><g class="nodes"><g transform="translate(114.4453125, 256.5)" id="classId-User-0" class="node default"><rect height="497" width="212.890625" y="-248.5" x="-106.4453125" class="outer title-state" style=""/><line y2="-218.5" y1="-218.5" x2="106.4453125" x1="-106.4453125" class="divider"/><line y2="237.5" y1="237.5" x2="106.4453125" x1="-106.4453125" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -17.7890625, -241)" height="18" width="35.578125" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">User</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -207)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -185)" height="18" width="92.9375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string name</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -163)" height="18" width="73.375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string pw</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -141)" height="18" width="96.53125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+bool deleted</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -119)" height="18" width="158.75"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+datetime last_access</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -97)" height="18" width="79.609375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dob</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -75)" height="18" width="99.171875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string weight</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -53)" height="18" width="77.8125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string sex</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -31)" height="18" width="126.71875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dirty_thirty</span></div></foreignObject><foreignObject transform="translate( -98.9453125, -9)" height="18" width="135.640625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string dirty_dozen</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 13)" height="18" width="197.890625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string member_since_date</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 35)" height="18" width="115.1875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string birthdate</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 57)" height="18" width="82.25"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string mail</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 79)" height="18" width="121.390625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string nickname</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 101)" height="18" width="92.0625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string notes</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 123)" height="18" width="97.40625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string phone</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 145)" height="18" width="109.84375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string address</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 167)" height="18" width="93.828125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int family_id</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 189)" height="18" width="163.21875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+blob membership_pdf</span></div></foreignObject><foreignObject transform="translate( -98.9453125, 211)" height="18" width="132.078125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string user_token</span></div></foreignObject></g></g><g transform="translate(348.4140625, 605.5)" id="classId-Family-1" class="node default"><rect height="79" width="65.6875" y="-39.5" x="-32.84375" class="outer title-state" style=""/><line y2="-9.5" y1="-9.5" x2="32.84375" x1="-32.84375" class="divider"/><line y2="28.5" y1="28.5" x2="32.84375" x1="-32.84375" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -25.34375, -32)" height="18" width="50.6875" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">Family</span></div></foreignObject><foreignObject transform="translate( -25.34375, 2)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject></g></g><g transform="translate(328.4140625, 256.5)" id="classId-Role-2" class="node default"><rect height="123" width="115.046875" y="-61.5" x="-57.5234375" class="outer title-state" style=""/><line y2="-31.5" y1="-31.5" x2="57.5234375" x1="-57.5234375" class="divider"/><line y2="50.5" y1="50.5" x2="57.5234375" x1="-57.5234375" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -17.3359375, -54)" height="18" width="34.671875" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">Role</span></div></foreignObject><foreignObject transform="translate( -50.0234375, -20)" height="18" width="43.140625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int id</span></div></foreignObject><foreignObject transform="translate( -50.0234375, 2)" height="18" width="92.9375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string name</span></div></foreignObject><foreignObject transform="translate( -50.0234375, 24)" height="18" width="100.046875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+string cluster</span></div></foreignObject></g></g><g transform="translate(114.4453125, 605.5)" id="classId-UserRole-3" class="node default"><rect height="101" width="98.171875" y="-50.5" x="-49.0859375" class="outer title-state" style=""/><line y2="-20.5" y1="-20.5" x2="49.0859375" x1="-49.0859375" class="divider"/><line y2="39.5" y1="39.5" x2="49.0859375" x1="-49.0859375" class="divider"/><g class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"></span></div></foreignObject><foreignObject transform="translate( -35.125, -43)" height="18" width="70.25" class="classTitle"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">UserRole</span></div></foreignObject><foreignObject transform="translate( -41.5859375, -9)" height="18" width="83.171875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int user_id</span></div></foreignObject><foreignObject transform="translate( -41.5859375, 13)" height="18" width="78.71875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">+int role_id</span></div></foreignObject></g></g></g></g></g></svg>
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@ -27,7 +27,8 @@ CREATE TABLE IF NOT EXISTS "family" (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "role" (
|
CREATE TABLE IF NOT EXISTS "role" (
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"name" text NOT NULL UNIQUE
|
"name" text NOT NULL UNIQUE,
|
||||||
|
"cluster" text
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "user_role" (
|
CREATE TABLE IF NOT EXISTS "user_role" (
|
||||||
@ -220,3 +221,20 @@ CREATE TABLE IF NOT EXISTS "distance" (
|
|||||||
"distance_in_km" integer NOT NULL
|
"distance_in_km" integer NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS prevent_multiple_roles_same_cluster
|
||||||
|
BEFORE INSERT ON user_role
|
||||||
|
BEGIN
|
||||||
|
SELECT CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM user_role ur
|
||||||
|
JOIN role r1 ON ur.role_id = r1.id
|
||||||
|
JOIN role r2 ON r1."cluster" = r2."cluster"
|
||||||
|
WHERE ur.user_id = NEW.user_id
|
||||||
|
AND r2.id = NEW.role_id
|
||||||
|
AND r1.id != NEW.role_id
|
||||||
|
)
|
||||||
|
THEN RAISE(ABORT, 'User already has a role in this cluster')
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::{sqlite::SqliteQueryResult, FromRow, SqlitePool};
|
use sqlx::{sqlite::SqliteQueryResult, FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
use super::user::User;
|
use super::user::User;
|
||||||
|
|
||||||
@ -22,6 +24,15 @@ impl Family {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn insert_tx(db: &mut Transaction<'_, Sqlite>) -> i64 {
|
||||||
|
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
||||||
|
.execute(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
result.last_insert_rowid()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert(db: &SqlitePool) -> i64 {
|
pub async fn insert(db: &SqlitePool) -> i64 {
|
||||||
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
let result: SqliteQueryResult = sqlx::query("INSERT INTO family DEFAULT VALUES")
|
||||||
.execute(db)
|
.execute(db)
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
#[derive(FromRow, Serialize, Clone)]
|
#[derive(FromRow, Serialize, Clone, Deserialize, Debug)]
|
||||||
pub struct Role {
|
pub struct Role {
|
||||||
pub(crate) id: i64,
|
pub(crate) id: i64,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
|
pub(crate) cluster: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
impl Role {
|
||||||
pub async fn all(db: &SqlitePool) -> Vec<Role> {
|
pub async fn all(db: &SqlitePool) -> Vec<Role> {
|
||||||
sqlx::query_as!(Role, "SELECT id, name FROM role")
|
sqlx::query_as!(Role, "SELECT id, name, cluster FROM role")
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -21,7 +22,7 @@ impl Role {
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE id like ?
|
WHERE id like ?
|
||||||
",
|
",
|
||||||
@ -31,12 +32,41 @@ WHERE id like ?
|
|||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, name: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"
|
||||||
|
SELECT id, name, cluster
|
||||||
|
FROM role
|
||||||
|
WHERE id like ?
|
||||||
|
",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_cluster_tx(db: &mut Transaction<'_, Sqlite>, name: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"
|
||||||
|
SELECT id, name, cluster
|
||||||
|
FROM role
|
||||||
|
WHERE cluster = ?
|
||||||
|
",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE name like ?
|
WHERE name like ?
|
||||||
",
|
",
|
||||||
@ -51,7 +81,7 @@ WHERE name like ?
|
|||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, name
|
SELECT id, name, cluster
|
||||||
FROM role
|
FROM role
|
||||||
WHERE name like ?
|
WHERE name like ?
|
||||||
",
|
",
|
||||||
|
@ -490,6 +490,17 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn has_membership_pdf_tx(&self, db: &mut Transaction<'_, Sqlite>) -> bool {
|
||||||
|
match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Some(a) if a.is_empty() => false,
|
||||||
|
None => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
|
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -502,6 +513,21 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
.into_iter().map(|r| r.name).collect()
|
.into_iter().map(|r| r.name).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn real_roles(&self, db: &SqlitePool) -> Vec<Role> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Role,
|
||||||
|
"SELECT r.id, r.name, r.cluster
|
||||||
|
FROM role r
|
||||||
|
JOIN user_role ur ON r.id = ur.role_id
|
||||||
|
JOIN user u ON u.id = ur.user_id
|
||||||
|
WHERE ur.user_id = ? AND u.deleted = 0;",
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn has_role_tx(&self, db: &mut Transaction<'_, Sqlite>, role: &str) -> bool {
|
pub async fn has_role_tx(&self, db: &mut Transaction<'_, Sqlite>, role: &str) -> bool {
|
||||||
if sqlx::query!(
|
if sqlx::query!(
|
||||||
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
|
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
|
||||||
@ -697,14 +723,16 @@ ORDER BY last_access DESC
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) {
|
pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) -> Result<(), String> {
|
||||||
|
let mut db = db.begin().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut family_id = data.family_id;
|
let mut family_id = data.family_id;
|
||||||
|
|
||||||
if family_id.is_some_and(|x| x == -1) {
|
if family_id.is_some_and(|x| x == -1) {
|
||||||
family_id = Some(Family::insert(db).await)
|
family_id = Some(Family::insert_tx(&mut db).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.has_membership_pdf(db).await {
|
if !self.has_membership_pdf_tx(&mut db).await {
|
||||||
if let Some(membership_pdf) = data.membership_pdf {
|
if let Some(membership_pdf) = data.membership_pdf {
|
||||||
let mut stream = membership_pdf.open().await.unwrap();
|
let mut stream = membership_pdf.open().await.unwrap();
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
@ -714,7 +742,7 @@ ORDER BY last_access DESC
|
|||||||
buffer,
|
buffer,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
}
|
}
|
||||||
@ -735,28 +763,29 @@ ORDER BY last_access DESC
|
|||||||
family_id,
|
family_id,
|
||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
|
|
||||||
// handle roles
|
// handle roles
|
||||||
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
|
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
|
||||||
.execute(db)
|
.execute(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for role_id in data.roles.into_keys() {
|
for role_id in data.roles.into_keys() {
|
||||||
self.add_role(
|
let role = Role::find_by_id_tx(&mut db, role_id.parse::<i32>().unwrap())
|
||||||
db,
|
.await
|
||||||
&Role::find_by_id(db, role_id.parse::<i32>().unwrap())
|
.unwrap();
|
||||||
.await
|
self.add_role_tx(&mut db, &role).await?;
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.commit().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_role(&self, db: &SqlitePool, role: &Role) {
|
pub async fn add_role(&self, db: &SqlitePool, role: &Role) -> Result<(), String> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||||
self.id,
|
self.id,
|
||||||
@ -764,7 +793,40 @@ ORDER BY last_access DESC
|
|||||||
)
|
)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"User already has a role in the cluster '{}'",
|
||||||
|
role.cluster
|
||||||
|
.clone()
|
||||||
|
.expect("db trigger can't activate on empty string")
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_role_tx(
|
||||||
|
&self,
|
||||||
|
db: &mut Transaction<'_, Sqlite>,
|
||||||
|
role: &Role,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO user_role(user_id, role_id) VALUES (?, ?)",
|
||||||
|
self.id,
|
||||||
|
role.id
|
||||||
|
)
|
||||||
|
.execute(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"User already has a role in the cluster '{}'",
|
||||||
|
role.cluster
|
||||||
|
.clone()
|
||||||
|
.expect("db trigger can't activate on empty string")
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_role(&self, db: &SqlitePool, role: &Role) {
|
pub async fn remove_role(&self, db: &SqlitePool, role: &Role) {
|
||||||
@ -1234,7 +1296,8 @@ mod test {
|
|||||||
membership_pdf: None,
|
membership_pdf: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let user = User::find_by_id(&pool, 1).await.unwrap();
|
let user = User::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
|
||||||
|
@ -200,7 +200,8 @@ async fn fees_paid(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap())
|
user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap())
|
||||||
.await;
|
.await
|
||||||
|
.expect("paid role has no group");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,9 +306,10 @@ async fn update(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
user.update(db, data.into_inner()).await;
|
match user.update(db, data.into_inner()).await {
|
||||||
|
Ok(_) => Flash::success(Redirect::to("/admin/user"), "Successfully updated user"),
|
||||||
Flash::success(Redirect::to("/admin/user"), "Successfully updated user")
|
Err(e) => Flash::error(Redirect::to("/admin/user"), e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/user/<user>/membership")]
|
#[get("/user/<user>/membership")]
|
||||||
@ -394,7 +396,9 @@ async fn create_scheckbuch(
|
|||||||
|
|
||||||
// 4. Add 'scheckbuch' role
|
// 4. Add 'scheckbuch' role
|
||||||
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
||||||
user.add_role(db, &scheckbuch).await;
|
user.add_role(db, &scheckbuch)
|
||||||
|
.await
|
||||||
|
.expect("new user has no roles yet");
|
||||||
|
|
||||||
// 4. Send welcome mail (+ notification)
|
// 4. Send welcome mail (+ notification)
|
||||||
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
||||||
@ -434,10 +438,14 @@ async fn schnupper_to_scheckbuch(
|
|||||||
user.remove_role(db, &paid).await;
|
user.remove_role(db, &paid).await;
|
||||||
|
|
||||||
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap();
|
||||||
user.add_role(db, &scheckbuch).await;
|
user.add_role(db, &scheckbuch)
|
||||||
|
.await
|
||||||
|
.expect("just removed 'schnupperant' thus can't have a role with that group");
|
||||||
|
|
||||||
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
|
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
|
||||||
user.add_role(db, &no_einschreibgebuehr).await;
|
user.add_role(db, &no_einschreibgebuehr)
|
||||||
|
.await
|
||||||
|
.expect("role doesn't have a group");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
user.send_welcome_email(db, &config.smtp_pw).await.unwrap();
|
||||||
|
@ -3,3 +3,35 @@ INSERT INTO user(name) VALUES('Marie');
|
|||||||
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz'));
|
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz'));
|
||||||
INSERT INTO user(name) VALUES('Philipp');
|
INSERT INTO user(name) VALUES('Philipp');
|
||||||
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
|
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
|
||||||
|
|
||||||
|
ALTER TABLE "role" ADD COLUMN "cluster" text;
|
||||||
|
CREATE TRIGGER IF NOT EXISTS prevent_multiple_roles_same_cluster
|
||||||
|
BEFORE INSERT ON user_role
|
||||||
|
BEGIN
|
||||||
|
SELECT CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM user_role ur
|
||||||
|
JOIN role r1 ON ur.role_id = r1.id
|
||||||
|
JOIN role r2 ON r1."cluster" = r2."cluster"
|
||||||
|
WHERE ur.user_id = NEW.user_id
|
||||||
|
AND r2.id = NEW.role_id
|
||||||
|
AND r1.id != NEW.role_id
|
||||||
|
)
|
||||||
|
THEN RAISE(ABORT, 'User already has a role in this cluster')
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=2;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=3;
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=5;
|
||||||
|
UPDATE role SET 'cluster'='skill' WHERE id=6;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=7;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=8;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=9;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=14;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=17;
|
||||||
|
UPDATE role SET 'cluster'='financial' WHERE id=18;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=20;
|
||||||
|
UPDATE role SET 'cluster'='membership_type' WHERE id=22;
|
||||||
|
@ -79,9 +79,12 @@
|
|||||||
<div class="w-full grid gap-3 mt-3">
|
<div class="w-full grid gap-3 mt-3">
|
||||||
<input type="hidden" name="id" value="{{ user.id }}" />
|
<input type="hidden" name="id" value="{{ user.id }}" />
|
||||||
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
|
{# for cluster, r in roles | group_by(attribute="cluster") #}
|
||||||
|
{# cluster #}
|
||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
{{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles, disabled=allowed_to_edit == false) }}
|
{{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles, disabled=allowed_to_edit == false) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{# endfor #}
|
||||||
<hr class="sm:col-span-2 lg:col-span-4 my-3" />
|
<hr class="sm:col-span-2 lg:col-span-4 my-3" />
|
||||||
{% if user.membership_pdf %}
|
{% if user.membership_pdf %}
|
||||||
<a href="/admin/user/{{ user.id }}/membership"
|
<a href="/admin/user/{{ user.id }}/membership"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user