diff --git a/docs/model/db-schema.png b/docs/model/db-schema.png index 11d86d9..e338c2c 100644 Binary files a/docs/model/db-schema.png and b/docs/model/db-schema.png differ diff --git a/docs/model/db-schema.xml b/docs/model/db-schema.xml index 349e685..914728c 100644 --- a/docs/model/db-schema.xml +++ b/docs/model/db-schema.xml @@ -1,2 +1,2 @@ -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 \ No newline at end of file +7Z1bd5u4Fsc/Tdaa85AsLgbjx1ycTKdJ25NmetqnLGoUmzkY+YCcxv30RwKEcaW4YIyQgrK6Zoy4GOu/9UNsaW+d2JfLl5vEXy3uYACiE8sIXk7sqxML/40m+H+kZJOXmBNjlJfMkzAoyrYFn8OfoCg0itJ1GIB050AEYYTC1W7hDMYxmKGdMj9J4I/dw55gtPutK38OmILPMz9iS/8TBmiRl3rWeFv+JwjnC/rNplv84qVPDy5+SbrwA/ijUmRPT+zLBEKUf1q+XIKI1B6tl/y861f2ljeWgBjVOSH6cuqubtLR/+6SD8vT0d1P+O/NqUNvDm3oLwYBroBiEyZoAecw9qPptvQiges4AOSyBt7aHnML4QoXmrjwH4DQplDTXyOIixZoGRV7wUuIvpLTz8ZOsfmtuBr5fPVS3dgUG08wRpcwgkl2m7aR/ZGD4uCcCI2Lp/c/QQIf4J0fb/I912FEv5ScX9yRaeNttv6KKk3hOpmBfZVmFYboJ3OA9hxoOfmBpEorX1HocwPgEqBkgw9IQOSj8HnX5vzCdOflceWpn2CIb9oyinZ2ao7cs/FoUv6NC/MsWt2pNTkzXLvc7Tm735D/4uKiWxPC1epvKoetyAHpvtswjN3vtXdMEn/IL0m3KnWwLcrMtokJm/2asKWyCXdumZY5OptM9lrm7m67lmlyvog+ajalMU52L5U3V+ZSB9jh3fXy/fzv1V/vzqPl85dv04vzp/jU9Xqxwxjf9VdqbGQjM8Mzh25uLTHb2pRbFZODMeBYnFExc7Nq42fOb638OFZq0sfTb0nrGYLs2fZ2EWeb7qEGO9k1fW/nutbI7sp69z6qnv1oXVRO6i9XEThbJfAZxH6MlfrVvnGXZkU+Iv97Zs0pvkkqvU1MAffKkB/GIClMaAajyF+lYXZ4XrIIo+DW38A1oheiWxdP4QsI7vMunJk1lh+3+GJp1c6KmyG7/Sicx/jzDNsZ+caLBKT4Xm79FBVHlF0wI7vZBP4XVHDsWd9t182+N4oq5YEDvGDUxLCfQYLAy147LO2JVnwJRLcwsB/b7qY1Lg5aVLuatHHwrHfHPBrbgsvYwqvS41+PQj+6x31vP55nVrArMqnqIIGrB9qESUHWkwDJFBsWKuWs1HqMiYSLUAZBsjMCT/Tc7xAhuCw2kqI+yotmdeFc4H+4di4Jqhx8u5d429xu43/k8AQ/jGNsBn6Y6QewofwAKaql9P5G9Hv9KT5qim13pvWY0frTe1xT1+/ZbtVezbPXL3+reWs5q807ezjiWn2Ksua7CIMAxC10cmvrVBGGNkwxuniMLoGP/BSgR3LeOd717sPD9GZ637NMlLr5sRfpyp+F8fw2P9OVRseXXQ2r7c0U2d5sS7NVEFvrPki709puqfXRGysIQno987hNsbRreZFqj15Fav7zVCNrP/JJQ1L2jUWTtBuSTnonads3EpVIWl+e3kjKvjTMEuAjEDz6xMWRU/Th3d3088P53achcbS+eLJwdMSKqTnaDUdNq2+QjtjXyjcL0tKw5QUpHbOoyLFe4U4pGDpHG2gnDUfZPsqb8qQdw7dtsCIJbW902JMd5SjeBNNXpdJjHC3sgD8tYKetepy26ow6M4S2rpmhdnrKFtRqiIMndneNnvX7EDBLg+Qjd3tK05a32+OwrhvFXG9V/Zze9NvT9bGEtjA9YCyKpjytxdKUdRf83XsPt7uXSEf+oWKHfaeP/SXIefrl/P7yz3PMU+MPy3H+JTtX+1FOFo66ekhDFEd5QxpCOeoOaEjDlX9Iw2UfayhE0eAp2kA6aSja1setKdpqQEMsRlkP+tvFqFdbn74wOjYYOQKQzpJwhUIYF+MZ068PQyJofdVkIehE90OFEdTpm6CTAXVEJ/J3RCdsRzQbcnokw8I5QK/OH6YDAmgD0aQBqO6CCgOo1ztAB9QFncjfBS2D0Ct6gDgYMD/V64CaBjtmqwHaDUDLVCu9AdQ02r5vKETQrWnLjFD2nYCGymTV8sgMMA3NL9pERlmoOjYZVTVUO4LqqG+oji1G7Lc8Tl/atrxUHbNzDvHdpqVfdLiDTA3Ek4aluoMqjKXj3lnK9k+v+55BWhellUmmh7ZO+TusY7a/WqD1MV3PQJqSelZoeukx2apgP1VHJIqbnN87W1lX+ZDYKn+M4ph1b1O2rhIQgIHj9SiBikLx6rH+cY3XjvDae/CTx/p8BoTX0tTlxavH+mlIC8NV/oi/YAGDAWclaiCfNHDVgaXC4Np7LJTHOoGGBFf5A0091nEzi/w0DZ/CmY8y98AmRWA5ZMaqF27q6XBTYYztPU7Ka+sMUpmw8gefeqz7JkwfV+vvUTjLkXrx8ePt9PzDkJCqXuSp13YOo0ZqXaSOeo+ZmrR1AKmM1EltvXqLAGBdNvjCPpmAlRP1r88fB4XT+pLJglPT0FOthPG09wgq02B9eIMB6tbU5SWqabBeuXUKkgG/9jeRTRaqOrqTKgyqvUdVucp2Ug/QR/5Oqct2aHTK96biyQJSt21/RYO0Lkid3qOr3AGtQuTW16c3kLJjhjrle0PtZOGoaXCSNeT5xHfncLwqo84qjipKNxH/1KVhAfviWLkLp3rd2YPO+3Do62j9eZSyLJ1aLjA+kMTiW+uW9+lqmuxro2JOng5TizdRUJZlVUxTu9KFMbX3JVNNk301fcthy1vrlpmp7BukTjDeTDtpaOq0datqmqqzbKrptH12qjwwWZq6xGjlLEg29Hy5TXSTB6s67EMYVvtfRdXkLCmmCFcPaY/yx3mYnBXF9PBkY/nkwamO8BCHU978OcE4VTbG45D2KH9Qh8lZUkwPUjZVTx6asuMU0WyWnnFDId/6CsjuzAPfn36xOzJW6QPvaVbLMhq7fai0+5Lu8Ihrdbb+sdl6et1gn7AN5ntJM1LJmb33pkcqXQVcP5xJeHqk8hAFpXnOtp5pp5mq0EglZyLfmx6pdBVwA3EW0NUjlc20k4em2gckjKb9j1Ry1t9VBKVHGKl0FXAJcVbVHfxIZQPdpMEqJ5e5xmpHWJVgpJKTGX04XC1tXWKucpKd+2u0gEmINpVlloaG1gbSyYNWHf0hDq39j1pyEqOr4wAo7s9o00QVCAfh5DrfWXNpcFhVMADE1Wk/xGGVl/dDMFYHlPhja9sSM5SzbKSeW9dYPmlwylkYUuO0I5zWjknvUO4Bpf/Y2rbMONUJQNqrJw1NPfaFDwRzQN+vMIUWcA5jP5puSy8SuI4DEBR1vD3mFmbikFbxD0BoU1Siv0YQFy3QMir2gpcQfaXNCX/+Rj6fOcXW1Utl19WGbsT4B3+lFyAblbPI5va0bIueR9S89pdhRAruITYVSDWG62RGbnCB0Co9sRxiug6uT/IfckB6NocQ26a/CtOzGTYwsmOWZodeP+XXxB9/uWrFcsh2ZY6ekf1ldxicJ0lmddN7bNcP8M6PN/mO65BImN06a3hULXrnrz/ZC2wi+mB77Ui6fgJRfa/JJiDyUfgMdm6kA4NkH+9FShq6gvuJ5UakMWfORcudo7Km3uykzz4S1BQYKnoCp5yhgHJ1sF1UdccqPT/pwCeTV//J9Pqcz1fk7kxttp/xpud8KrAckclZj0jP+TxEwT39v7FYRfUsJWFM5agtmqnsYK0yC7790q3eYriuXgpMUuKsRpQFOQ050ftxViMSS9WJHkkXRlXO3E/BVJ20HeBTlqkT+YfQLfo+WnUoEGfAI/FZ5lS9On+YDgmpDWSTBamWXpFIHFJ58z7FMtVSd0milky1FFiPyOKsRwTiYLhEbSKaPERtG7KiidpquqdoorZ9J1GXqPVzCvVHVPYdgi7BMGCq1hdOFqqaeiVigVTlzPYU/e6v7GzPI8QnKbAYsclZjTiCecaoHKo3049304f7b3/QD8TiLWNkW+6gYuyPs0yx4C5s2zymGrat5oKK7sK2jf1VGLZbW5cXtpbBjiYPfJ3iJrLJglVz0tb5prFaH6ujvrFqToY0xX5SX6H++qxst0ZHLDWWTx6c6pQl4nA67h+nyqYsOaQ9KpCiZMIOdOiIpabqyUJTy3YYhURGLJVRSt9o49IRS8eIWCrdC7+NWCpnBEoSsmRx1mzd5qcHOiF9Bwnp6XAqHQjgYcnjYKlkVQdWoCcoHegiKdtPq4z0PLm7ewpx1nl9y9FJlgLrvFqcdV4Vczx2GJ3URME9fT1LrKJ6fEcYU+s+QjtUm/WTqJOQ7hCNFBjRMVlnhs5I30w7eWiqJ3wKoykvI71gmio73/MIg+WmAlM+TXam4NAz0jfRTRqs0m/TWO0eq9yM9GK5arX186jMVau+Yr1xleqxM5U+AMMEagPB5AGqzvMkDqi8PPSCgdrWydM3UI027VP+rE9lKjgmK0n+84Y8u7OBfNLg1WafjxqvXeGVl49eLF5t9mmqjlf1CIQtzV1iwtqcCZ+8xe2HjNoGOkqDWktn1xOGWm6uesE9WWUXAT2k6yN/Pj2sP8tVPZG+qXzy4FTH1ovDKScuSXTPVdnY+kPao/yx9JbNur71RPqm6nVPU7yZQIgq+24Sf7W4gwEgR/wf7Z1vc6O2Foc/TWZ6X2zGgMH2yyTrbHubdHey6e22bzJao9i0GHlA3o376a/4I7Aj2QsGREAns9MaGctY5yc94nCkc2HdrF8+hGizuicu9i/MkftyYb2/ME1rbJjsf3HJLi0xTNNOS5ah52ZlRcFn71+cFY6y0q3n4ujgREqIT73NYeGCBAFe0IMyFIbk++Fpz8Q//NYNWmKh4PMC+WLpH55LV2np1JwU5T9jb7ni32w4s/SdNeInZ78kWiGXfN8rsuYX1k1ICE1frV9usB+3Hm+X6a+3j389/EFXV784j/+gL3/8c799l1Z2W+Uj+U8IcUCbrTqzbkR3vL2wy5ovOyQhXZElCZA/L0qvQ7INXBzXOmJHxTl3hGxYocEK/8aU7jItoC0lrGhF1372Ln7x6Jf445cTOzv8M6stfv3+Zf9glx08k4DeEJ+EyWVao+QvPilwr2KZsOL5w784JI/kHgW79J1bz+dfGn8+uyLDYscl2zRr+4hswwU+dV6mGorCJT5VYdZ74mbeE2hmsg+YrDENd+yEEPuIet8OVYyyzrDMz8s/+ol47IeYo6znvrNnzuVkPMv/pmkNWTc2Z6PLmeWM+J9hH35B+nuzOgtRsZZGu73TNvEJ0amrsEevv3e/PvYirZIf7TVBUZQIuYKorW5FbQ5O1K2L1XHGl7PZKbGO9v7MUlqVfMtsdliv8epK084r1NSULsed6DJgF/2Fiy8+SGR5afPDQpnJ0S4/2pMgCbBEgaMD2VdTfYuqnfBpxg+H4pEieU+tw1HQsZwzJcxG9BMdxXasUoKuPIw7U+vgi6bj0Q+uczaq9wHHstsHRQa9b8jfZraN0Hrj48tNSL7hAAVMZ6+7LJsFbuKXFH1NOmjE2pir2YrVzSayFHkBDrMOuCC+jzaRl5yelqw8371DO7KlvCJ+dP3svWD3IZ31Gkn//37HKov2u052MfHbyPeWAXu9YF0n/sbrEEfsWu5QRLMz8lnrKLnYkPyD94gzNb9ajpN8r+/vlbs2nrrj2n31Gw4pfjnZt3IBTOxDAWR6+F5M2k1+ymp/ws57saw/HozgVdXhCOo4KgbWHtRD/gO7gUHBMtHFodnjxndDsnnkY1JckHQ7HM6Z1Ghu4D07BGzYZUU0GenjN338zD/7lVBK1tlBmDVHXmnSFPY1+8ca5yYem212uTfs2CiO2b/49JDNQAImDOQlFsVMOt9xRM+3vV3a9pmprZKmttqy9ESw9KdfWTvd/mpUs3hyB4sKi9c25n53T/jPGvXZT7rzynNdHJxvJae0lfbMwjtlWatklRWtVLk25LMeEiCKr+OJUNTGtGwqWN9FFEWYPsUfu2Jv/fLb4/zD/KFjMfCxPj33OtqghRcs79JPOm9FLS/Hh2+jYp8+rZ5S1SmQzwwwoQYTZWcErWGCuzTPNnXjQwJ2PV6f0WyHn5W2ztDxwJ0EEj6krdg3THSiEu2wwB+cABfa5sKscy5Y+nAhlzWAwRgLZl+EmH2l+4Ri11YKhcdf7uefH6/uP2mEhQoi0Y8LotMRuNAKFwyzczDUdSH2CQzl7TN4MIj+xO2G3TNg7blQXiP6cUF0Qw7fCV3/MZFskK86qNQb40X/X/YQMfMSREfNB48Qm9aGPNjooLtPJd3dHrelDq5OmO1VtX5519KJp4gyY7c2EpiiozAewd/M2N3whC+XNkz4eKfes3zPfML7OrE708mJSZ/Z7KSvTHUqhFPXcwh4qPP0UC0eRHfh753P7dvzB+TSBjyYossvQGucAuJ/Vw83P18xQIx+Mm37P28dFN0oRD8wQPChKjCUvUlsDwyiu3C4VICww7wpRIcf9agPWGgk1nCgWIBgQ1VYkD49VMoFXrEWXIB4w9zsknhDHC1Cb0M9EmQPD+dfHnVCQiNxhsNEgiX6HgEJLSHB7hwJGkUa5sIGJFii6zB5ZvwUB5WkRHh/9TjXiAgVxKEfESDCUBkRpp0TQaMQQwtCDPOmEH2GOHA15kEjkYUD5YHoaAQetMODfNe77nhQ10PYJx5MS9tn6DzgAYySRapJ4z8JD5x1e6xQQSzaEWIsuhyBEC0RYtw1Icaiw3DIcUi5tgESY9F7yFolyp8q6PvMuYJI9IOD6HwEOLQEh0nncBB9h7ddr2Eoy4a9ZQ7nDgPj0hYbPCtEv2LGiqdou8BRFNuzRwscmoRFeZXoB4u6sYwAi/Lr3TqHhehY1AkWk9IWGzwsRKcjh8UmxC7WnBflhaIdL3hEDfCifV50vkDaFt2MGvEilzrwwhZdkHFPZtf8xKpeEVfjLTUryEQ/WsByaWW06Hy9tC36HXWiBSyfzptC9EkufBRF3rO3QDTxSO0iitc6QwOWUh9vG1hKrQwana+ltnu7lroBZMDa6rwpRM+kFz1ttl99b5Ey4vrjx7v51W86MQLWVR9vG1hXrYoR487XVXNZa8kIWGedy0D0RrIqURw2myLiv58/asUHWGR9XCuiwxL40BIfOl9k7YheRo0cT7nUgRCO6IEk35NdufX0MlWQhn6EgEXXygjR+aJrR6NF17mwgQeO6FyEhF9VRaIfGWD5tSoy2J0vv3Y0Wn7twPJr3hR83Q5k/DpfI9qBYSI6ILPMUYfBcEflAvmjTuKjqvLeTfmjiQwn07KZhfNtoBrnyQQcj+dZf1Le/CfyRynNFjoR/Y5Dzh+VSxumDxPR0dizWLYW80dV0Il2SUMn4HVUhQeZqNTiQXQ6Dnnflgn4HfOmEP2OkD+qkkL0AwM4HVWBoexNYntg6K3PsX60wgR8kLwpuA8C8oacow7tCDGFPR9VEUKaSkopIqZ13Yg9unGYlrfP4JEgehQhYKGqSPQjA2z4qIwMsmBntWSo60HsExlge8e8KURnIgQsVNSIfmAQ/ZD+YhFdSndjiI6KZhhhC85iir8+v5J3HLaA8PR5cb4Aq4ct2K/DFso+qsj3om+eKuB+PHP4Kb9X4FsJW5iK7schhy1MwePIm4J7viFsoZ5OtJtIzMD3qAoPnYctzETX45DDFmbgfcybQvQ+QthCJYXoBwZwPaoCQ+dhC7Peeh7rhy3MwBOZN4XoidQ9bKGCOvQjBCSYUUWI7sMWZnXdiH1GBKSXyZtC9DCiLV2R0KO7vcTGulGikbQyw6SEwXNjASbax0TnMQzGSPQn9sfHlF3f6HybFWoHVhgj0d94kORYM0pU0YaGmICEMsowIdu1TTEm6noXe/QcopA2MMEYSTLIQBx0VZloiAdIHaMKD6Zs6zbFeOht7phz+j0kiynaQnQzQjB0VZVoSIeZIAXsLjG/oWej6oosSYD8eVF6HcbXg93MmMU5dyRRQdzN/8aU7jJroS0lrGhF1372Ln7x6Jf445emnR3+yYcL9vr9y/7Bjh8E7Ld+4TXEB8lnLm1+WHwsOeKfi3Vzi9aeHxc8ECZKwtVEtuEivsIVpRvWvnbcSWzWxvF/4hOiyyUhzHho40WXCybl+I1FlJx6+5zWyV6+qnVPo/HxXlz1KPlLrtC9CsNE3/MH1oMeyT0Kdukbt15sw+TSy0o8syH/OT8iGuX0PnYiT8UXS+Fkhwmxj6j3bf+kNqjGuSruMhhnOokwfbowHT8eShJPuuksad56gw3efzt7Do75gMnnQZJ47jxz98GI195EyIDwzDPNb5R3th0P35ebuz1riw7TIcfvF+qGma9hiP5PiOA/RyknZr+TRpVTqjol0oFQTWWQkMhKNSRED2pvcmK9uqcouFLWXhCsWbSF6BFNVv9qnHu9ij40xATEayrDhCSkXzUmehuwWRsSEK5ZtIUYr5m4i55in3qKifdXj3OtGNFIsOZAGcHjzIER7TNCFtSvGBJmXfdibyFhljfX4CHBjb6nAhy4GiOigjg0RAQEaqpDhCSgXzUihhCpeR4iIGyzaAtJ2GaWGU9jTDQSsNl7TLwsvc3fNPz98w26dv66+uZ+HU/ewX2EOkhIwvlbg4TU2BrcRZwUuVaAkLaEeAeh0c4RNbUxZDYcAScE8yujgyyaX/UtRG+j+evvHVFoXStIHGkL8ZmUT9KtqlNGfJh/vJ8/Pvz5E38R9yxzNLZMR6d96aqIRkN61E2yBvQoT49x5/TgNfcx3qkJfsxK22zw/LDEG81thEOdI54qyEM/UljibSmQoi1STLonRd0nU2/BD1XWQrm2gQuGJT6jgk0lKstEQz7U3fcY+FBhdV33fKjrduwVH+zSFho+H0QHJOwqUVUlGuJhKkhB5a4SF3tbSlzyDSZgU4ns0ssqPDPhjzaVMLij64e7SuSLjN7KthKW6A8tckFiSP6oav8IHobBb4hlI+VUMrTl413z0uCihgluZTCW96udyP4oM3d789ux6B4d9PYR4/I3IYOf4o5FV2fPvOJtbh9RQSknprlms9PcMtUpkQ4E9CuDRNk5QYuQEH2l/dmg/5yuD1H8RVuI/k5IA1lNIxriAYI1leFBlglSMR50jtUcQ6xm0RZirKZGIf219aEhJyAsUxknpPkg1YKCr0zWExQQlFkIQfQ6LoiLNSVEI+GYAyWELfoogRBtEUKWClIxIXobjtlAHshc60AIw5aEZyY7labNqHP4fgWZaMgLCM9UxwtZTkjFvBAdjf15MtEEMiBis2gL0Q2ZIMN79tL1wk/RLqJ4rTU7GondHCo76m5/DOyolzBSMTvqOh/79Eg71zaAwuDXAUu/6shEPz44kFhNHR8km0go5oNT1/XYJz7k2gY+GI7ohISlX1VVMmQ8yLczdAQlvN18wi0vl5I2UOOZdbOPfor5VijuXR4RlaHEdl6JIV2ulX2seSGIo0eWsTfed2boS6veUGpe81AGTtlNDfOpR+N73kLOxfP2RS0fattBWl7pFYtPIQayquqkrrWaO0pbQnwc0TNnczMrqmqqZMjbBkibBrIsKgKDylS80ivWIMPiSYUDIsSnDsV6quGGPtYUhXZEyDfLAiS0jASVaXflptY1FUqFDeGGDgVDfNaA18jztcRCBV3oxwXYeEERF5Sm2pXbWoM0iqdFDmAwRE+ix/qFR7c6LKmtqw798AAbL6jCg8o0u3Jb93bbhbp40HLLBXlTiP5EViVyEUUpG/77+eNv1zrBAbJgHW8b2G1BFRw6T6/Lb156DYeyfV7LnRXkZpdsrKBdaHtdkWgHBj42ABjaBoPSzLpyW/d2h4Xq5sl1DWAwRV+idjHtdTWiHxdsQQgDS2bSTSR81hd/mDaEu5hazxrCDkNC6L56QrRZ3RMXx2f8Hw==7Z1vc5u4FsY/TWb2vmjGgMH4Zf442d5NNp00e7d9laFGseli5AW5jfvprwTCxhGkuDbiEE6ms2sExsBz9DN+dHQ4sS4Wz9ext5zfUp+EJ+bAfz6xLk9M/jcc8/+JlnXWYoxN2TKLA1+2bRs+Bj+IbBzI1lXgk2RnQ0ZpyILlbuOURhGZsp02L47p993Nnmi4+6lLb0aUho9TL1Rb/w58Ns9aXXO0bf+dBLN5/smGI89v4eUbyzNJ5p5PvxearMmJdRFTyrJXi+cLEoqrl1+Xf+0fX9n13ePF5cP1X8G5H52F/77Ldna1z1s2pxCTiB1316Y8NbbOrxfx+eWTizRmczqjkRdOtq3nMV1FPhF7HfCl7TY3lC55o8EbvxLG1jIWvBWjvGnOFqFcS54D9km8/XRky8XPcm/i9eVzcWEtF55oxC5oSOP0MK1B+ic2ivwzESa8eXL/g8T0gd560TpbcxWE+YeK98sjMiy+nJ24ONsXcfKTiyy3S+gqnpLXtpNhxLx4Rl7bob0JJd4JCV0QFq/5+2ISeiz4tnt0nuwMs812m7d+oAE/bnMge+47Y+icjobjzd9IBrzsx+/M8enAsTarXXv3E7LzkzvdRhW/1N66sNlSbJC8dhiDwe7nWjtRyl9ku8yXCtdg25RG8h5RbbUb1eZbj+rjR6tpDE/H41ejdXe1VStcSz4o/0JbbwJ0vLurrMMquzpWbA5bic2IH/SnPADFQhqap3a+uI3OdGm9WSqEIY1ISRQOCqFvFOP+1P5p5OuM3JFbl8eDZkLccndJaBnOr8bweLc3uDv7NYeW1oCWXxzfvHAlr03iLZYhOV3G9BuJvIjL8jLk+a3UUrxk3pc0wBN+jHk0WCI6+N0g84KIxDKqpjQMvWUSpJtnLfMg9G+8NV2xfEf50vlT8Ez8++zW0Uj7z/cbvrOkGHryYMRqLwxmEX895ZEmPvE8Jgk/lhsvYXKLza3fID3YmP5DCtR2zS+W46SfG4aFdt8mrj+sjPVvJGbk+ReiXY3NTYyZ9gtuOjLovm/vfc2R3GhevO+1B9URvRMy+8aHo8RHZTjw02eBF97z3wFeNEsjY1d4cfn9mC4f8k4sGtJ7EBJPeLCxjcQFJSIOLt7EUlaKlSF5yt/7hTJGF3Ihlpdjs9P0Utjn/B+/OBeCaDY/3Au+bGyX+T+xecy/xyMeGl6QCkh48HwnCTuq+hW3ijlQakptNaX0SFH6wx/8Ol39YeynePpD0NsqfrCYxQ6ffoPyi/8Uph16Hvg+iY6qklOuUkGWvFNqUcVVVPE95iWEPYq3nfFV7/98mFxP7lsWKadwtu15svSmQTS7yd7pQFHxeVfBYl8zdPa1MVJVD1XrfoE2pnRupP2y1EfvqcQP8v0ZjffDih+cbdHUMCpxmp1K16jainpQKGqYiFE9GB23jlGr1xjNIh0QR4eKHNOYeIz4j57wOjKGPry/nXx8OLv90G+KVokHBqOqB4QYbQSjhtk6Rw/1czrO0Qp9WuOoarqslvyOlCBGa2sHBqOqVdNLA21vk3ugKqe3E6pujBwDkb8Nk0r9cATk2MFRnm+w06vdkl5tD5uKDvNQAwfvjn6af/DKEEiZ2I2RwFTtIYFwMPBu/gapkBoH4gYp72wFRTrm0BX1s1vT75WbJFNrDzvUx0Ga1qVpmdZ6aaqaRH+1fi+s9edmFu2AaKo6PZG3IBlP/3d2f/H7Gefp4DfTtv8DnavtKAeGo5iXo4ujZWMfejmqmkS9giiwjBxT9XlYwEKkaH3pwFAU83B0UbR06EMrRq1DjZyOYxRYKo5VkopDkmkcLFlAIznyMfn00HOC7p+Co5WglurPIEEbIqjdOkH7nYSTxToggqr+SjoO9SgGkDOAXp49TPoN0CrRwAAUk2+0AdRtHaD9zr6xgGXfWKqxQiIf8VlDMjD4VM0YxGcz+NzUhGkPn4faNR3HpwsLn0PVUMkn06QXZTOX5u79Zc8pWqEcFIoOVS8GKdoQRYdtU3SoWjY9G5YfVmQhtgZS1Vfhp5VsbFAcU6ohHhiWqq4MsrQhlo5aZ6nq3ly1nTBaF6WFnNIj9s4hMLSqjotE62OympIkEde5Q9mkDbO1Qj0wbD00EwbZWj8Xv3W2qtZOz9k6AsZW1Y7J2bqMiU8QrzUFhIJXWzV0EK8N4bX1uU626vn0G69VlUPbwqut+jSih/FL/sg/YE59rFVURz4wcMWZT9rg2vrUJ1s1gXoOV2AzoWzVuJmGXpIET8HUY6k9sE4YWSBj66gIhrE4K0obY1ufFmV3dlpUM4QFNk3KVu2bIHlcrr6EwTRD6vnd3c3k7M+eIxX4FCkbp0jpQuqw9SlSzqEG0BtDKrApU45q2fAdeyLpKiPqfz/e9R2nwOdLOaqpgzhtCKetz5dyOjtfqhGcZrEPCKeqSbNKSIw/+euIBgaoOH9KG1Bbnz/l9Hv+lANs/pSjOjBYBX5f8cCAFGdS6QKp3fpMKudQV6fjIAU2k2qk+i5YBn5P7aBwdKSaNLKa+G72RqWKWFP8uDXFnbx3vTaPtfShqm5jMYIu0NHUH1XID+axqqoJ1LOa4iNgvs9I9X06ZvnorSlepR+UB6+M0ALSRdPWH6c6Uh2gnk1eHgEzgUaqCYQ1xfdSDgxH0QHSxdHWn6c66qwB1MjQ5AiYIeSqhhAWx62tGhSgulhWRxdQ23+yqnuop9Pt21IXWE0dV7VdcGxyX/HAgBRr6mgDaVnWnF6QHmrndBykwCrouKrjgmOTe2oHhqOqVxNOp8lp6dTHt/7UY2fqki9PL6JOjFB6xH2aHjUuXv96zeV+rfJOGYHNxp557KITdDwmVBR9gTI+6apOUM/GJ11g5s9YNX9wfPIX9IPyrTtGG0gXTVsfnxyrLlDPxifHwIygsWoE4fjkXsqB4Si6QLo42vr45LizJlAj45NjYKbQWDWFcHyytmpggIrllHUBtf3xyfGhns4bIyqwYspj1YbxVmxO44CtH7c3rAjVKuGgQNUYqPYNUrUhqrY+WGkMVHOnOz/45fENjqqZ7ABw0GoMVFNm5/lKCNVKzeBQFcsna6NqWZ0PzVQ91Orptocqox0SQkvqJWM63b7ywaEpFkrWRdPa080bVLuzlZKP1B2BlUY2BqoXgzl1+6oHB6ZjRSHiz0j+64pDaE5nNPLCybb1PKaryCe+vMbbbW5oKo7oFV8JY2t5Eb0Vo7xpzhahXEueA/Yp70789Wfx+tSWS5fPhVWX63wh4if8Kd+BWCi8Syxu35Yu5e8Tal55iyAUDfeUhwrNNaareCoOcM7YMjkxbRG6Nr+e4j9ig+R0RimPTW8ZJKdTHmBixTRJN716yvbJX77YayFyxHIhO2+Q/qVH6J/FcRp1k3se1w/01ovW2YqrQEiYHnomjdBjz7CTmubnV7VdntzB8i+/qg2rnp0Qk9Bjwbfdwzt+kBqqKyXL0uTPbD8xnVB08NRsNJ0Z21y9N5sCCqZIjeSVvGV4VzJYsHlg2C7TGoOagUlLx9PeqHBXqnNAK+RuTG3VIetZEqgMeEA3iYbqeGEa6K8o+MqN4kivopjApI2pJWrrZmqHnw3/4v57i+ED9AKWwmQYqgeWzoPC+u+1dINDVcxi0kbVkrxQ3VTtbBpTE0wFlsRkGGoWU+oQPApzM6Pq5dnDpO9I3T+FSS9Sc/8Bkdo8UssyQzUz1TzU63lLTDWhZS/lYhTUIZGPRK0jGhyiYvqSPqKWZIXqJupbyF86GlGhJTOZJclM8tkMSNUawsGhKqYx6aNqSVaobqp2No+pkSlMMvwhgVU1Z0KaFZXKoHo9ubudPNx//i1/ISLeHAwt0+n55PtKMeHA9tCS0Qjb2rAtSxrVDNt8zwjbYvgDgq2lujb4+OLasoHBqqXaO4jVprA6bB+rnX0m/HEUysIdEkZVqwZnNu0tHxyc4sPA9OF01D5O+/1EeBnukHCqejQ4tWlf9eDQ1FUU0jm1aTOd6XPeuXBqk76pTYYlHZ+fzm2qTBLUNLnJUq2pbV17goXsdRWyzwde8yGDMoC5JQDbUO34oTE81MbC+8GX/WyfeUylcjf3fTVUPbG+zWMaQstfGqoGV8csSs3zmKoUfOWu0NSrKCYzaWNq3a/QBtVWDbLuVLg7Uo+ElsE0VF0uLGq/n3ZwaIpJTNpoWlbXXjNNMYepJPohoVXNYcLS9vV1g4NVTFfShtXS6vZ6uZpbT8jVYvgD4qqtWjNT6hME6k8EAwNUW/VxEKhNAbWssL1moHY2UamZqvZZ+EMCakniUlq/JDsXzAOtJR8cvGLikj68llW414xX1fXpjqvaEGGh5TLZqleTEjZ4CrJJTY/JOmFkgaitpSMc1OLj7bWhtrT8vWbUHuoEdXz0ygb2fPtNWhCm3B8iHxicOlgqWh9OS2Ywacapc6gP1HGcOtAKRTuqU4Mp9/uq1zxN+WJMKSusu4695fyW+kRs8X8= \ No newline at end of file diff --git a/docs/sphinx/api.rst b/docs/sphinx/api.rst index 0f96866..5af1e03 100644 --- a/docs/sphinx/api.rst +++ b/docs/sphinx/api.rst @@ -28,4 +28,5 @@ Sample-DB API datasets dataset_table - provenance \ No newline at end of file + provenance + users \ No newline at end of file diff --git a/docs/sphinx/usage.rst b/docs/sphinx/usage.rst index 5e2fd54..87d82a3 100644 --- a/docs/sphinx/usage.rst +++ b/docs/sphinx/usage.rst @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . + Usage ===== @@ -72,7 +73,8 @@ You should get a similar output:: sampledb | collect_method | table | postgres sampledb | datasets | table | postgres sampledb | provenance | table | postgres - (3 rows) + sampledb | users | table | postgres + (4 rows) Setting up PL/pgSQL Triggers and Loading default script data diff --git a/docs/sphinx/users.rst b/docs/sphinx/users.rst new file mode 100644 index 0000000..7b0b692 --- /dev/null +++ b/docs/sphinx/users.rst @@ -0,0 +1,25 @@ +.. + This file is part of SAMPLE-DB. + Copyright (C) 2022 INPE. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + +Users +----- + +.. autoclass:: sample_db.models.users::Users + :members: + :special-members: __init__ + :member-order: bysource \ No newline at end of file diff --git a/sample_db/alembic/9f4a4b16344f_adding_user.py b/sample_db/alembic/9f4a4b16344f_adding_user.py new file mode 100644 index 0000000..96d13bd --- /dev/null +++ b/sample_db/alembic/9f4a4b16344f_adding_user.py @@ -0,0 +1,50 @@ +"""Adding user. + +Revision ID: 9f4a4b16344f +Revises: 90f91c523f48 +Create Date: 2022-11-11 14:59:20.900082 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '9f4a4b16344f' +down_revision = '90f91c523f48' +branch_labels = () +depends_on = '561ebe6266ad' # LCCS-DB stable 0.8.1 + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('users', + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=255), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('institution', sa.String(length=255), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('users_pkey')), + sa.UniqueConstraint('email', name=op.f('users_email_key')), + sa.UniqueConstraint('user_id', name=op.f('users_user_id_key')), + schema='sampledb' + ) + op.create_index(op.f('idx_sampledb_users_email'), 'users', ['email'], unique=False, schema='sampledb') + op.create_index(op.f('idx_sampledb_users_institution'), 'users', ['institution'], unique=False, schema='sampledb') + op.create_index(op.f('idx_sampledb_users_name'), 'users', ['name'], unique=False, schema='sampledb') + op.create_index(op.f('idx_sampledb_users_user_id'), 'users', ['user_id'], unique=False, schema='sampledb') + op.create_foreign_key(op.f('datasets_user_id_users_fkey'), 'datasets', 'users', ['user_id'], ['user_id'], source_schema='sampledb', referent_schema='sampledb', ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(op.f('datasets_user_id_users_fkey'), 'datasets', schema='sampledb', type_='foreignkey') + op.drop_index(op.f('idx_sampledb_users_name'), table_name='users', schema='sampledb') + op.drop_index(op.f('idx_sampledb_users_institution'), table_name='users', schema='sampledb') + op.drop_index(op.f('idx_sampledb_users_email'), table_name='users', schema='sampledb') + op.drop_index(op.f('idx_sampledb_users_user_id'), table_name='users', schema='sampledb') + op.drop_table('users', schema='sampledb') + # ### end Alembic commands ### diff --git a/sample_db/models/__init__.py b/sample_db/models/__init__.py index 732cf56..af18bd9 100644 --- a/sample_db/models/__init__.py +++ b/sample_db/models/__init__.py @@ -18,6 +18,7 @@ """SampleDB Provenance Model.""" from sample_db.models.datasets import CollectMethod, Datasets, DatasetView from sample_db.models.provenance import Provenance +from sample_db.models.users import Users -__all__ = ['Datasets', 'Provenance', 'CollectMethod', 'DatasetView'] +__all__ = ['Datasets', 'Users', 'Provenance', 'CollectMethod', 'DatasetView'] diff --git a/sample_db/models/dataset_table.py b/sample_db/models/dataset_table.py index a24ec37..4b6d4c4 100644 --- a/sample_db/models/dataset_table.py +++ b/sample_db/models/dataset_table.py @@ -29,6 +29,7 @@ from ..config import Config from .base import db, metadata +from .users import Users class DatasetType(UserDefinedType): @@ -81,7 +82,7 @@ def make_dataset_table(table_name: str, create: bool = False) -> Table: s_name = f"{Config.SAMPLEDB_SCHEMA}.dataset_{table_name}_id_seq" if create: - if not sqlalchemy.inspect(db.engine).has_table(table_name=f'dataset_{table_name}', schema=Config.SAMPLEDB_SCHEMA): + if not db.engine.dialect.has_table(table_name=f'dataset_{table_name}', connection=db.session, schema=Config.SAMPLEDB_SCHEMA): db.engine.execute(f"CREATE TABLE {Config.SAMPLEDB_SCHEMA}.dataset_{table_name} OF dataset_type") db.engine.execute(f"CREATE SEQUENCE {s_name}") @@ -119,6 +120,10 @@ def make_dataset_table(table_name: str, create: bool = False) -> Table: ForeignKeyConstraint(name=f"dataset_{table_name}_{klass.c.class_id.name}_fkey", columns=[klass.c.class_id], refcolumns=[LucClass.id], onupdate="CASCADE", ondelete="CASCADE"))) + db.engine.execute(AddConstraint( + ForeignKeyConstraint(name=f"dataset_{table_name}_{klass.c.user_id.name}_fkey", + columns=[klass.c.user_id], refcolumns=[Users.user_id], onupdate="CASCADE", + ondelete="CASCADE"))) else: raise RuntimeError(f'Table {table_name} already exists') else: diff --git a/sample_db/models/datasets.py b/sample_db/models/datasets.py index c965b08..7d4693e 100644 --- a/sample_db/models/datasets.py +++ b/sample_db/models/datasets.py @@ -34,10 +34,10 @@ from ..config import Config from .base import db as _db from .dataset_table import make_dataset_table +from .users import Users Feature = Dict[str, str] - class CollectMethod(BaseModel): """Collect Method Model.""" @@ -76,7 +76,7 @@ class Datasets(BaseModel): nullable=False) collect_method_id = Column(Integer, ForeignKey(CollectMethod.id, ondelete='CASCADE', onupdate='CASCADE'), nullable=True) - user_id = Column(Integer, nullable=False) + user_id = Column(Integer, ForeignKey(Users.user_id, ondelete='CASCADE'), nullable=False) __table_args__ = ( Index(None, user_id), @@ -147,13 +147,15 @@ def get_ds_table(cls, ds_name: str, ds_version: str) -> Union[Table, None]: f'LOWER(ds.name) = LOWER(\'{ds_name}\') AND ' \ f'LOWER(ds.version) = LOWER(\'{ds_version}\')' - res = _db.session.execute(expr).fetchone() - - if res: - return Table(res.table_name, _db.metadata, schema=Config.SAMPLEDB_SCHEMA, autoload=True, - autoload_with=_db.engine) - - return None + try: + res = _db.session.execute(expr).fetchone() + if res: + return Table(res.table_name, _db.metadata, schema=Config.SAMPLEDB_SCHEMA, autoload=True, + autoload_with=_db.engine) + + return None + finally: + _db.session.close() @property def ds_table(self) -> Union[Table, None]: diff --git a/sample_db/models/users.py b/sample_db/models/users.py new file mode 100644 index 0000000..cc29498 --- /dev/null +++ b/sample_db/models/users.py @@ -0,0 +1,44 @@ +# +# This file is part of SAMPLE-DB. +# Copyright (C) 2022 INPE. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +from bdc_db.sqltypes import JSONB +from lccs_db.models.base import BaseModel +from sqlalchemy import (Column, ForeignKey, Index, Integer, + PrimaryKeyConstraint, String) + +from ..config import Config + + +class Users(BaseModel): + """User Model.""" + + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + email = Column(String(255), nullable=False, unique=True) + name = Column(String(255), nullable=False) + institution = Column(String(255), nullable=False) + user_id = Column(Integer, nullable=False, unique=True) + + __table_args__ = ( + Index(None, email), + Index(None, name), + Index(None, institution), + Index(None, user_id), + dict(schema=Config.SAMPLEDB_SCHEMA), + ) \ No newline at end of file diff --git a/sample_db/version.py b/sample_db/version.py index 00f308f..68101ec 100644 --- a/sample_db/version.py +++ b/sample_db/version.py @@ -22,4 +22,6 @@ """ -__version__ = '0.9.1' + +__version__ = '1.0.0' +