FEA: introduced group membership states.

This commit is contained in:
Keanu D?lle 2021-11-09 00:18:45 +01:00
parent e1e3ca3406
commit dd0d248c17
27 changed files with 439 additions and 119 deletions

View File

@ -1,7 +1,7 @@
-- Your SQL goes here
create table if not exists group_entity_state
(
state_id uuid not null
state_id uuid default uuid_generate_v1() not null
constraint group_entity_state_pk
primary key,
name text not null,

View File

@ -0,0 +1,16 @@
<tr class="group_detailed_view_member_tr" data-member-id="{{entity_id}}">
<td><input type="checkbox" class="group_detailed_view_member_selected" data-member-id="{{entity_id}}"></td>
<td>{{firstname}}</td>
<td>{{lastname}}</td>
<td>
<select class="form-control group_detailed_view_member_state_select" data-member-id="{{entity_id}}" data-group-id="{{group_id}}">
<option value="null"></option>
{{#each states}}
<option value="{{state_id}}" {{#if active}}selected{{/if}}>{{name}}</option>
{{/each}}
</select>
</td>
<td>
<a href="/portal/mm/profile?action=view&id={{entity_id}}"><svg width="1.5em" height="1.5em"><use xlink:href="/img/bootstrap-icons.svg#eye-fill"></use></svg></a>
</td>
</tr>

View File

@ -201,6 +201,13 @@ th.rotate > div > span {
left: 50%;
z-index:1000;
}
.saved_animation{
position: absolute;
top: 40%;
left: 50%;
opacity: 0.4;
z-index:1000;
}
/* Application colour scheme: */
.acs-green{

View File

@ -343,4 +343,12 @@ $(document).ajaxStart(function() {
$(document).ajaxStop(function() {
$(".loading_animation").hide();
});
});
let show_saved_animation = function(){
$('.saved_animation').fadeIn(500).fadeOut(500);
$(document).mousemove(function(e){
$(".saved_animation").css({left:e.pageX+15, top:e.pageY});
});
$(document).off("mouseover");
}

View File

@ -1,9 +1,11 @@
var all_checked = false;
var all_members_checked = false;
var delete_list = [];
var active_group_id = "";
let all_checked = false;
let all_members_checked = false;
let delete_list = [];
let active_group_id = "";
$( document ).ready(function() {
$( document ).ready(async function() {
GroupModule.states = await GroupModule.load_states();
await GroupModule.load_templates();
$(".new_group_button").on("click", GroupModule.create_group_button_listener);
$(".groups_delete_button").on("click", GroupModule.delete_group_button_listener);
$(".check_all_groups").on("click",GroupModule.check_all_button_listener);
@ -22,7 +24,32 @@ $( document ).ready(function() {
});
//TODO: Remove add member to group in UI if user missing permission
GroupModule = ( function() {
var load_group_detailed_view = function(){
let states = {};
let templates = {};
let load_templates = async function(){
const mm_groups_members_row = $.get("/templates/mm_group_members_row.hbs");
const search = $.get("/templates/search.hbs");
await Promise.all([mm_groups_members_row, search]).then(function(res){
templates.mm_groups_members_row = Handlebars.compile(res[0]);
templates.search = res[1];
});
Handlebars.registerPartial('search', templates.search);
};
let load_states = async function(){
const res = await $.ajax({
type: "GET",
url: "/api/groups/states",
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Es ist ein Fehler aufgetreten!!");
},
});
if (is_ok(res)) {
return res;
}
};
let load_group_detailed_view = function(){
active_group_id = $(this).data("group-id");
$(".group_detailed_view").prop("hidden", false);
@ -32,7 +59,7 @@ GroupModule = ( function() {
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
@ -47,7 +74,7 @@ GroupModule = ( function() {
}
});
};
var clear_detailed_view = function(){
let clear_detailed_view = function(){
$(".group_detailed_view_member_tr").remove();
$("#group_detailed_view_name_input").val("");
$("#group_detailed_view_description_input").val("");
@ -56,7 +83,7 @@ GroupModule = ( function() {
$(".group_detailed_view_member_list").show();
$(".group_detailed_view_submit_core_data").show();
};
var set_group_detailed_view_core_data = function(name, description, readonly){
let set_group_detailed_view_core_data = function(name, description, readonly){
$("#group_detailed_view_name_input").val(name);
$("#group_detailed_view_description_input").val(description);
if(readonly){
@ -65,27 +92,51 @@ GroupModule = ( function() {
$(".group_detailed_view_submit_core_data").hide();
}
};
var set_group_detailed_view_member_list = function(members){
let set_group_detailed_view_member_list = function(members){
if(!members){
return;
}
$(members).each(function(index, value){
$(".group_detailed_view_tbody").append("<tr class=\"group_detailed_view_member_tr\" data-member-id=\""+value.entity_id+"\">\n" +
" <td><input type=\"checkbox\" class=\"group_detailed_view_member_selected\" data-member-id=\""+value.entity_id+"\"></td>\n" +
" <td>"+value.firstname+"</td>\n" +
" <td>"+value.lastname+"</td>\n" +
" </tr>");
$(members).each(function(index, member){
member.states = Object.assign({}, GroupModule.states);
$.each(member.states, function(index, state){
if(state.state_id === member.group_state){
state.active = true;
}else{
state.active = false;
}
});
$(".group_detailed_view_tbody").append(templates.mm_groups_members_row(member));
});
$(".group_detailed_view_member_state_select").off("change").on("change", update_group_member_state);
};
let update_group_member_state = function(){
$.ajax({
type: "PUT",
url: "/api/groups/"+$(this).data("group-id")+"/members/"+$(this).data("member-id")+"/state/"+this.value,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
show_saved_animation();
}
}
});
};
var create_group_button_listener = function(){
let create_group_button_listener = function(){
if($.trim($('#new_group_name').val()) == ''){
alert("Fehler: Es wurde kein Gruppenname angegeben!");
return
}
var create_group_data = {};
var group_data = {};
var role_permissions = [];
let create_group_data = {};
let group_data = {};
let role_permissions = [];
group_data.group_name = $("#new_group_name").val();
if(!$.trim($("#new_group_description").val()) == ''){
@ -93,7 +144,7 @@ GroupModule = ( function() {
}
$(".new_group_role_row").each(function(index, row){
var group_role_perm = {};
let group_role_perm = {};
group_role_perm.role_id = $(row).data("role-id");
group_role_perm.permission_groups_core_edit = $(row).find("input.permission_groups_core_edit").prop("checked");
group_role_perm.permission_groups_delete = $(row).find("input.permission_groups_delete").prop("checked");
@ -110,7 +161,7 @@ GroupModule = ( function() {
create_group_request(create_group_data);
};
var create_group_request = function(data){
let create_group_request = function(data){
$.ajax({
type: "POST",
url: "/api/groups",
@ -118,7 +169,7 @@ GroupModule = ( function() {
data: JSON.stringify(data),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
@ -127,14 +178,14 @@ GroupModule = ( function() {
}
});
};
var delete_group_confirmation_check = function(){
let delete_group_confirmation_check = function(){
if($("#groups_delete_modal_confirmation").val() === "GRUPPEN LÖSCHEN"){
$("#groups_delete_modal_delete_button").prop("disabled", false);
}
};
var delete_group_button_listener = function(){
let delete_group_button_listener = function(){
delete_list = [];
var delete_list_names = "";
let delete_list_names = "";
$(".group_entry_checkbox").each(function(){
if($(this).prop('checked')) {
delete_list.push($(this).data("group-id"));
@ -153,7 +204,7 @@ GroupModule = ( function() {
$("#groups_delete_modal_group_list").html(delete_list_names);
$("#groups_delete_modal").modal();
};
var check_all_button_listener = function(){
let check_all_button_listener = function(){
if(all_checked){
$(".group_entry_checkbox").prop('checked', false);
all_checked = false;
@ -162,7 +213,7 @@ GroupModule = ( function() {
all_checked = true;
}
};
var delete_groups_request = function(){
let delete_groups_request = function(){
if(delete_list.length===0){
return;
}
@ -173,7 +224,7 @@ GroupModule = ( function() {
data: JSON.stringify(delete_list),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
@ -182,30 +233,36 @@ GroupModule = ( function() {
}
});
};
var add_member_click_listener = function(element){
let add_member_click_listener = function(element){
GroupModule.add_member_to_group(active_group_id, $(element).data("entity-id"), $(element).data("firstname"), $(element).data("lastname"));
}
var add_member_to_group = function(group_id, member_id, member_firstname, member_lastname){
let add_member_to_group = function(group_id, member_id, member_firstname, member_lastname){
$.ajax({
type: "PUT",
url: "/api/groups/"+group_id+"/members/"+member_id,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
$(".group_detailed_view_tbody").append("<tr class=\"group_detailed_view_member_tr\" data-member-id=\""+member_id+"\">\n" +
" <td><input type=\"checkbox\" class=\"group_detailed_view_member_selected\" data-member-id=\""+member_id+"\"></td>\n" +
" <td>"+member_firstname+"</td>\n" +
" <td>"+member_lastname+"</td>\n" +
" </tr>");
let member = {};
member.entity_id = member_id;
member.group_id = group_id;
member.firstname = member_firstname;
member.lastname = member_lastname;
member.states = Object.assign({}, GroupModule.states);
$.each(member.states, function(index, state){
state.active = false;
});
$(".group_detailed_view_tbody").append(templates.mm_groups_members_row(member));
$(".group_detailed_view_member_state_select").off("change").on("change", update_group_member_state);
}
}
});
};
var check_all_members_listener = function(){
let check_all_members_listener = function(){
if(all_members_checked){
$(".group_detailed_view_member_selected").prop('checked', false);
all_members_checked = false;
@ -214,9 +271,9 @@ GroupModule = ( function() {
all_members_checked = true;
}
};
var remove_member_from_group_listener = function(){
let remove_member_from_group_listener = function(){
$(".group_detailed_view_member_selected").each(function(){
var member_to_delete = $(this).data("member-id");
let member_to_delete = $(this).data("member-id");
if($(this).prop("checked")) {
$.ajax({
type: "DELETE",
@ -224,7 +281,7 @@ GroupModule = ( function() {
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
@ -239,10 +296,10 @@ GroupModule = ( function() {
}
})
};
var save_core_data_button_listener = function(){
var group = {};
let save_core_data_button_listener = function(){
let group = {};
group.group_name = $("#group_detailed_view_name_input").val();
var description = $("#group_detailed_view_description_input").val();
let description = $("#group_detailed_view_description_input").val();
if(description!=null && description!==""){
group.group_description = description;
}
@ -254,7 +311,7 @@ GroupModule = ( function() {
data: JSON.stringify(group),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
alert("Es ist ein Fehler aufgetreten!!");
},
success: function (data) {
if(is_ok(data)) {
@ -275,5 +332,8 @@ GroupModule = ( function() {
check_all_members_listener: check_all_members_listener,
remove_member_from_group_listener: remove_member_from_group_listener,
save_core_data_button_listener: save_core_data_button_listener,
load_states: load_states,
states: states,
load_templates: load_templates,
};
})();

View File

@ -12,4 +12,7 @@
<body>
<span class="loading_animation" style="display: none">
<img src="/img/ajax-loader.gif" width="35">
</span>
<span class="saved_animation" style="display: none">
<svg width="2em" height="2em"><use xlink:href="/img/bootstrap-icons.svg#cloud-check"></use></svg>
</span>

View File

@ -122,6 +122,7 @@
</svg></button></th>
<th>Vorname</th>
<th>Nachname</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>

View File

@ -1 +1 @@
v0.2-54-geed5237
v0.2-61-ge1e3ca3

View File

@ -27,7 +27,7 @@ pub fn get_raw_member_search_result(
match short_member {
Ok(raw_member) => Ok(raw_member),
Err(e) => {
warn!("Couldn't load RawMemberSearchResult: {}", e);
error!("Couldn't load RawMemberSearchResult: {}", e);
return Err(e);
}
}
@ -39,20 +39,21 @@ pub fn get_member_search_result(
caller_entity_id: uuid::Uuid,
) -> Result<MemberSearchResult, diesel::result::Error> {
let member = get_raw_member_search_result(settings, member_entity_id)?;
let groups = get_groups_for_member(settings, member_entity_id);
let groups = get_groups_for_member(settings, member_entity_id)?;
let readable = check_access_to_member_or_group(
settings,
member_entity_id,
groups,
groups.clone().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect(),
caller_entity_id,
"modules.member_management.profile.view".to_string(),
crate::permissions::modules::member_management::profile::VIEW.to_string(),
);
Ok(MemberSearchResult {
entity_id: member.entity_id,
entity_id: member_entity_id,
firstname: member.firstname,
lastname: member.lastname,
readable,
groups,
})
}
@ -104,11 +105,11 @@ pub fn get_member_search_result_by_name(
let mut members: Vec<MemberSearchResult> = vec![];
for member in raw_members {
let groups = get_groups_for_member(settings, member.entity_id);
let groups = get_groups_for_member(settings, member.entity_id)?;
let readable = check_access_to_member_or_group(
settings,
member.entity_id,
groups,
groups.clone().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect(),
caller_entity_id,
"modules.member_management.profile.view".to_string(),
);
@ -117,6 +118,7 @@ pub fn get_member_search_result_by_name(
firstname: member.firstname,
lastname: member.lastname,
readable,
groups,
});
}
@ -128,6 +130,7 @@ use diesel::sql_types::BigInt;
use crate::database::controller::members::TempQuery;
use crate::helper::order_enum::Order;
use std::convert::TryFrom;
use crate::modules::member_management::model::groups::Group;
#[derive(Queryable, Clone, Deserialize, Serialize, QueryableByName)]
#[table_name = "members"]
@ -247,7 +250,7 @@ pub fn check_permissions_for_member_list(settings: &State<Settings>, mle: Vec<Me
let mut res : Vec<MemberListEntryWithPermissions> = vec![];
for entry in mle{
let groups = get_groups_for_member(settings, entry.entity_id);
let groups : Vec<Group> = get_groups_for_member(settings, entry.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
let mut matrix = PermissionMatrix{
read: false,
delete: false,

View File

@ -4,7 +4,7 @@ use crate::database::model::groups::{GroupMemberCount, RawGroup};
use crate::helper::settings::Settings;
use crate::modules::member_management::model::groups::{Group, GroupData, GroupUpdateData};
use crate::schema::groups_entities::dsl::group_id;
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl};
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl, sql_query};
use rocket::State;
use std::collections::HashMap;
@ -138,36 +138,46 @@ pub fn get_groups(
Ok(groups)
}
pub fn get_groups_for_member(settings: &State<Settings>, member_id2: uuid::Uuid) -> Vec<Group> {
//TODO: return errors
use diesel::sql_types::*;
#[derive(Serialize, Deserialize, Queryable, Clone, QueryableByName)]
pub struct GroupMembership{
#[sql_type = "Uuid"]
pub(crate) entity_id: uuid::Uuid,
#[sql_type = "Uuid"]
pub(crate) group_id: uuid::Uuid,
#[sql_type = "Text"]
pub(crate) group_name: String,
#[sql_type = "Nullable<Text>"]
pub(crate) group_description: Option<String>,
#[sql_type = "Nullable<Uuid>"]
pub(crate) state: Option<uuid::Uuid>,
}
#[derive(Serialize, Deserialize, Queryable, Clone)]
pub struct GroupEntityState{
pub(crate) state_id: uuid::Uuid,
pub(crate) name: String,
pub(crate) description: Option<String>,
pub(crate) unique: bool,
}
pub fn get_groups_for_member(settings: &State<Settings>, member_id2: uuid::Uuid) -> Result<Vec<GroupMembership>, diesel::result::Error> {
use crate::schema::groups::dsl::*;
use crate::schema::groups_entities::dsl::entity_id as groups_entities_entity_id;
use crate::schema::groups_entities::dsl::groups_entities;
let connection = establish_connection(settings);
let data: Result<Vec<RawGroup>, diesel::result::Error> = groups
.left_join(groups_entities.on(group_id.eq(crate::schema::groups::dsl::entity_id)))
.filter(groups_entities_entity_id.eq(member_id2))
.select((entity_id, group_name, group_description))
.load(&connection);
let data: Result<Vec<GroupMembership>, diesel::result::Error> = sql_query("SELECT ge.entity_id, group_id, group_name, group_description, state FROM groups LEFT JOIN groups_entities ge on groups.entity_id = ge.group_id WHERE ge.entity_id = $1").bind::<diesel::pg::types::sql_types::Uuid, _>(member_id2).get_results(&connection);
match data {
Ok(data) => {
let mut groups_result: Vec<Group> = Vec::new();
for raw_group in data {
groups_result.push(Group {
entity_id: raw_group.group_id,
group_name: raw_group.name,
selected: false,
group_description: raw_group.description,
});
}
groups_result
}
Ok(data) => Ok(data),
Err(e) => {
warn!("Couldn't get groups for member: {}", e);
vec![]
error!("Couldn't get groups for member: {}", e);
return Err(e);
}
}
}
@ -261,3 +271,17 @@ pub fn update_group_core_data(settings: &State<Settings>, group_id2: uuid::Uuid,
}
}
}
pub fn get_group_entity_states(settings: &State<Settings>) -> Result<Vec<GroupEntityState>, diesel::result::Error>{
use crate::schema::group_entity_state::dsl::*;
let connection = establish_connection(settings);
match group_entity_state.load(&connection){
Ok(res) => Ok(res),
Err(e) => {
error!("Couldn't get group entity states: {}", e);
Err(e)
}
}
}

View File

@ -225,7 +225,7 @@ pub fn get_members_by_user_uuid(uuid: uuid::Uuid, settings: &State<Settings>) ->
let permissions = get_member_permissions_by_uuid(raw_member.entity_id, settings);
let permissions = permissions.unwrap_or(vec![]);
let groups = get_groups_for_member(settings, raw_member.entity_id);
let groups = get_groups_for_member(settings, raw_member.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
members.push(Member::from((raw_member, permissions, groups)));
}
@ -246,7 +246,7 @@ pub fn get_member_by_uuid(uuid: uuid::Uuid, settings: &State<Settings>) -> Optio
let permissions = get_member_permissions_by_uuid(raw_member.entity_id, settings);
let permissions = permissions.unwrap_or(vec![]);
let groups = get_groups_for_member(settings, raw_member.entity_id);
let groups = get_groups_for_member(settings, raw_member.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
Some(Member::from((raw_member, permissions, groups)))
}
@ -276,7 +276,7 @@ pub fn get_members(
for raw_member in raw_members {
let permissions = get_member_permissions_by_uuid(raw_member.entity_id, settings);
let permissions = permissions.unwrap_or(vec![]);
let groups = get_groups_for_member(settings, raw_member.entity_id);
let groups = get_groups_for_member(settings, raw_member.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
let member = Member::from((raw_member, permissions, groups));
members.push(member);

View File

@ -1,10 +1,12 @@
use crate::database::controller::api_members::get_member_search_result;
use crate::database::controller::connector::establish_connection;
use crate::database::controller::groups::get_groups_for_member;
use crate::database::controller::groups::{get_groups_for_member, GroupEntityState, GroupMembership};
use crate::database::model::api_members::RawMemberSearchResult;
use crate::helper::check_access::check_access_to_member_or_group;
use crate::helper::settings::Settings;
use crate::modules::api::members::get_member::MemberSearchResult;
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl};
use diesel::result::{DatabaseErrorKind, Error};
use rocket::State;
pub fn get_raw_member_search_results_in_group(
@ -34,57 +36,175 @@ pub fn get_raw_member_search_results_in_group(
}
}
pub fn get_member_search_results_in_group(
#[derive(Serialize, Deserialize, Queryable, Clone)]
pub struct MemberGroupView {
pub(crate) entity_id: uuid::Uuid,
pub(crate) group_id: uuid::Uuid,
pub(crate) group_state: Option<uuid::Uuid>,
pub(crate) firstname: String,
pub(crate) lastname: String,
pub(crate) readable: bool,
}
fn convert_to_group_view(msr: MemberSearchResult, group: uuid::Uuid) -> MemberGroupView {
for single_group in msr.groups {
if single_group.group_id == group {
return MemberGroupView {
entity_id: msr.entity_id,
group_id: group,
group_state: single_group.state,
firstname: msr.firstname,
lastname: msr.lastname,
readable: msr.readable,
};
}
}
MemberGroupView {
entity_id: msr.entity_id,
group_id: group,
group_state: None,
firstname: msr.firstname,
lastname: msr.lastname,
readable: msr.readable,
}
}
pub fn get_members_in_group(
settings: &State<Settings>,
group_entity_id: uuid::Uuid,
caller_entity_id: uuid::Uuid,
) -> Result<Vec<MemberSearchResult>, diesel::result::Error> {
) -> Result<Vec<MemberGroupView>, diesel::result::Error> {
let raw_memberlist = get_raw_member_search_results_in_group(settings, group_entity_id)?;
let mut memberlist: Vec<MemberSearchResult> = vec![];
let mut memberlist: Vec<MemberGroupView> = vec![];
for member in raw_memberlist {
let readable = check_access_to_member_or_group(
memberlist.push(convert_to_group_view(get_member_search_result(
settings,
member.entity_id,
get_groups_for_member(settings, member.entity_id),
caller_entity_id,
crate::permissions::modules::member_management::profile::VIEW.to_string(),
);
memberlist.push(MemberSearchResult {
entity_id: member.entity_id,
firstname: member.firstname,
lastname: member.lastname,
readable,
});
)?, group_entity_id))
}
Ok(memberlist)
}
pub fn add_member_to_group(settings: &State<Settings>, member_id : uuid::Uuid, group_id2 : uuid::Uuid) -> Result<(), diesel::result::Error>{
pub fn add_member_to_group(
settings: &State<Settings>,
member_id: uuid::Uuid,
group_id2: uuid::Uuid,
) -> Result<(), diesel::result::Error> {
use crate::schema::groups_entities::dsl::*;
let connection = establish_connection(settings);
match diesel::insert_into(groups_entities).values((group_id.eq(&group_id2), entity_id.eq(&member_id))).execute(&connection){
match diesel::insert_into(groups_entities)
.values((group_id.eq(&group_id2), entity_id.eq(&member_id)))
.execute(&connection)
{
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't add member {} to group {}: {}", member_id, group_id2, e);
error!(
"Couldn't add member {} to group {}: {}",
member_id, group_id2, e
);
Err(e)
}
}
}
pub fn remove_member_from_group(settings: &State<Settings>, member_id : uuid::Uuid, group_id2 : uuid::Uuid) -> Result<(), diesel::result::Error>{
/// Retrieves specified state from database
/// Parameters:
/// * state: state_id as [uuid::Uuid]
pub fn get_state(settings: &State<Settings>, state: uuid::Uuid) -> Result<GroupEntityState, diesel::result::Error>{
use crate::schema::group_entity_state::dsl::*;
let connection = establish_connection(settings);
match group_entity_state.filter(state_id.eq(state)).get_result(&connection){
Ok(state) => Ok(state),
Err(e) => {
warn!("Couldn't retrieve state: {}", e);
Err(e)
}
}
}
use diesel::dsl::count;
/// Checks if state is unique for member
/// Returns Ok(true) if state is unique for member, Ok(false) if state isn't unique.
pub fn check_state_unique(settings: &State<Settings>, member: uuid::Uuid, group: uuid::Uuid, state_id: uuid::Uuid) -> Result<bool, diesel::result::Error>{
use crate::schema::groups_entities::dsl::*;
let connection = establish_connection(settings);
match groups_entities.filter(state.eq(state_id)).filter(group_id.ne(group)).filter(entity_id.eq(member)).select(count(state)).get_result::<i64>(&connection){
Ok(count) => {
if(count == 0){
Ok(true)
}else{
Ok(false)
}
},
Err(e) => {
error!("Couldn't check if state is unique for memer: {}", e);
Err(e)
}
}
}
/// Update members state for group
pub fn add_member_to_state(settings: &State<Settings>, member: uuid::Uuid, group: uuid::Uuid, state_id: Option<uuid::Uuid>) -> Result<(), diesel::result::Error>{
use crate::schema::groups_entities::dsl::*;
let connection = establish_connection(settings);
match diesel::delete(groups_entities).filter(group_id.eq(group_id2)).filter(entity_id.eq(member_id)).execute(&connection){
// Check if state has unique flag and check for uniqueness
match state_id{
Some(state_id) => {
let res = get_state(settings, state_id)?;
if res.unique{
match check_state_unique(settings, member, group, state_id){
Ok(uni) => {
if !uni{
return Err(diesel::result::Error::DatabaseError(DatabaseErrorKind::UniqueViolation, Box::new(String::from("Violates uniqueness for state. There may be only one group where this entity can have this state."))))
}
}
Err(e) => return Err(e)
}
}
}
None => {}
}
match diesel::update(groups_entities.filter(entity_id.eq(member)).filter(group_id.eq(group))).set(state.eq(state_id)).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't remove member {} from group {}: {}", member_id, group_id2, e);
error!("Couldn't change state for member: {}", e);
Err(e)
}
}
}
}
pub fn remove_member_from_group(
settings: &State<Settings>,
member_id: uuid::Uuid,
group_id2: uuid::Uuid,
) -> Result<(), diesel::result::Error> {
use crate::schema::groups_entities::dsl::*;
let connection = establish_connection(settings);
match diesel::delete(groups_entities)
.filter(group_id.eq(group_id2))
.filter(entity_id.eq(member_id))
.execute(&connection)
{
Ok(_) => Ok(()),
Err(e) => {
error!(
"Couldn't remove member {} from group {}: {}",
member_id, group_id2, e
);
Err(e)
}
}
}

View File

@ -147,6 +147,8 @@ fn rocket() -> _ {
modules::api::groups::update::put_member_in_group,
modules::api::groups::delete::delete_member_from_group,
modules::api::groups::update::update_group,
modules::api::groups::update::put_member_in_state,
modules::api::groups::read::read_group_entity_states,
modules::api::units::read::read_unit_list,
modules::api::units::update::put_member_in_unit,
modules::api::units::delete::delete_member_from_unit,

View File

@ -51,7 +51,7 @@ pub fn settings_permissions(cookie: SessionCookie, settings: &State<Settings>) -
let roles = match get_roles(settings){
Ok(roles) => roles,
Err(e) => {error!("Couldn't get roles!");return Err(Status::InternalServerError)}
Err(e) => {error!("Couldn't get roles: {}", e);return Err(Status::InternalServerError)}
};
let module = SettingsModule {

View File

@ -43,7 +43,7 @@ pub fn settings_roles(cookie: SessionCookie, settings: &State<Settings>) -> Resu
let roles = match get_roles(settings){
Ok(roles) => roles,
Err(e) => {error!("Couldn't get roles!");return Err(Status::InternalServerError)}
Err(e) => {error!("Couldn't get roles: {}", e);return Err(Status::InternalServerError)}
};
let module = SettingsModule {

View File

@ -1,7 +1,7 @@
use crate::database::controller::groups::{get_group, get_raw_groups};
use crate::database::controller::groups::{get_group, get_group_entity_states, get_raw_groups, GroupEntityState};
use crate::database::controller::groups_permissions::get_group_role_permissions;
use crate::database::controller::members::check_access_to_resource;
use crate::database::controller::members_groups::get_member_search_results_in_group;
use crate::database::controller::members_groups::{get_members_in_group, MemberGroupView};
use crate::database::model::groups::RawGroup;
use crate::helper::session_cookies::model::SessionCookie;
use crate::helper::settings::Settings;
@ -27,7 +27,7 @@ pub struct CallerGroupPermissions {
#[derive(Serialize, Deserialize, Queryable, Clone)]
pub struct DetailedGroup {
pub(crate) group: RawGroup,
pub(crate) members: Option<Vec<MemberSearchResult>>,
pub(crate) members: Option<Vec<MemberGroupView>>,
pub(crate) group_role_permissions: Option<Vec<GroupRolePermission>>,
pub(crate) caller_permissions: CallerGroupPermissions,
}
@ -141,7 +141,7 @@ pub fn read_group_detailed(
Err(e) => return Err(translate_diesel(e)),
};
let members = match get_member_search_results_in_group(settings, entity_id, caller.entity_id) {
let members = match get_members_in_group(settings, entity_id, caller.entity_id) {
Ok(members) => members,
Err(e) => return Err(translate_diesel(e)),
};
@ -188,3 +188,17 @@ pub fn read_group_simple(
Ok(Json(group))
}
/// Get a list of all group entity states
#[get("/api/groups/states", format = "json")]
pub fn read_group_entity_states(
settings: &State<Settings>,
cookie: SessionCookie,
) -> Result<Json<Vec<GroupEntityState>>, Json<ApiErrorWrapper>> {
let _caller = parse_member_cookie(cookie.member)?;
match get_group_entity_states(settings){
Ok(states) => Ok(Json(states)),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -4,7 +4,7 @@ use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid_string};
use rocket::serde::json::Json;
use crate::database::controller::members_groups::add_member_to_group;
use crate::database::controller::members_groups::{add_member_to_group, add_member_to_state};
use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::controller::members::check_access_to_resource;
use crate::modules::member_management::model::groups::{GroupUpdateData};
@ -40,3 +40,53 @@ pub fn update_group(settings: &State<Settings>, cookie: SessionCookie, update_gr
Err(e) => Err(translate_diesel(e))
}
}
/// Put member in state for group
/// Parameters:
/// * group_id: group_id as uuid in string
/// * member_id: members entity_id as uuid in string
/// * state_id: states id as uuid in string OR null if state should be set to NULL
#[put("/api/groups/<group_id>/members/<member_id>/state/<state_id>", format = "json")]
pub fn put_member_in_state(settings: &State<Settings>, cookie: SessionCookie, group_id: String, member_id: String, state_id: String) -> Result<(), Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let member_id = parse_uuid_string(member_id)?;
let group_id = parse_uuid_string(group_id)?;
let state_id = if(state_id == "null"){
None
}else{
Some(parse_uuid_string(state_id)?)
};
if !check_access_to_resource(settings, caller.entity_id, group_id, crate::permissions::modules::member_management::groups::members::EDIT){
return Err(Json(ApiError::new(403, "Keine Berechtigung Gruppenmitglieder zu ändern!".to_string()).to_wrapper()))
}
match add_member_to_state(settings, member_id, group_id, state_id){
Ok(_) => Ok(()),
Err(e) => Err(match e {
diesel::result::Error::DatabaseError(kind, _) => match kind {
diesel::result::DatabaseErrorKind::UniqueViolation => Json(
ApiError::new(
409,
"Dieses Mitglied hat diesen Status bereits in einer anderen Gruppe!".to_string(),
)
.to_wrapper(),
),
_ => Json(
ApiError::new(500, "Es ist ein Datenbankfehler aufgetreten.".to_string())
.to_wrapper(),
),
},
diesel::result::Error::NotFound => Json(
ApiError::new(
404,
"Die angeforderte Ressource konnte nicht gefunden werden.".to_string(),
)
.to_wrapper(),
),
_ => Json(
ApiError::new(500, "Es ist ein Datenbankfehler aufgetreten.".to_string()).to_wrapper(),
),
})
}
}

View File

@ -22,7 +22,7 @@ pub fn api_members_delete(
if !check_access_to_member_or_group(
&settings,
member_id,
groups,
groups.unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect(),
caller.entity_id,
"modules.member_management.profile.delete".to_string(),
) {

View File

@ -8,6 +8,7 @@ use rocket::serde::json::Json;
use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::controller::api_members::MemberListOrder;
use std::convert::TryFrom;
use crate::database::controller::groups::GroupMembership;
#[derive(Serialize, Deserialize, Queryable, Clone)]
pub struct MemberSearchResult {
@ -15,6 +16,7 @@ pub struct MemberSearchResult {
pub(crate) firstname: String,
pub(crate) lastname: String,
pub(crate) readable: bool,
pub(crate) groups: Vec<GroupMembership>,
}
#[derive(Serialize, Deserialize, Queryable, Clone)]

View File

@ -58,7 +58,7 @@ pub fn api_communication_targets_create(
let caller = parse_member_cookie(cookie.member)?;
let communication_target = communication_target.into_inner();
let groups = get_groups_for_member(settings, communication_target.entity_id);
let groups = get_groups_for_member(settings, communication_target.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if !check_access_to_member_or_group(settings, communication_target.entity_id, groups, caller.entity_id, crate::permissions::modules::member_management::profile::communication::EDIT.to_string()){
return Err(Json(ApiError::new(401, "Keine Berechtigung, ein Kommunikationsziel hinzuzufügen.".to_string()).to_wrapper()))
}
@ -89,7 +89,7 @@ pub fn api_communication_targets_update(
Err(e) => return Err(translate_diesel(e))
};
let member_groups = get_groups_for_member(settings, old_target.entity_id);
let member_groups = get_groups_for_member(settings, old_target.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if old_target.entity_id != caller.entity_id{ //if Member edits own communication target, do not check permissions
if !check_access_to_member_or_group(settings, old_target.entity_id, member_groups, caller.entity_id, "modules.member_management.profile.communication.edit".to_string()) {
@ -166,7 +166,7 @@ pub fn api_communication_targets_delete(
}
};
let groups = get_groups_for_member(settings, target.entity_id);
let groups = get_groups_for_member(settings, target.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if !check_access_to_member_or_group(settings, target.entity_id, groups, member.entity_id, crate::permissions::modules::member_management::profile::communication::EDIT.to_string()){
return Err(Json(ApiError::new(401, "Keine Berechtigung Kommunikationseintrag zu löschen!".to_string()).to_wrapper()))

View File

@ -22,7 +22,7 @@ pub fn api_member_qualifications_read(
let caller = parse_member_cookie(cookie.member)?;
let member_id = parse_uuid_string(member_id)?;
let groups = get_groups_for_member(settings, member_id);
let groups = get_groups_for_member(settings, member_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if !check_access_to_member_or_group(settings, member_id, groups, caller.entity_id, crate::permissions::modules::member_management::profile::qualifications::VIEW.to_string()){
return Err(Json(ApiError::new(401, "Keine Berechtigung Qualifikationen für dieses Mitglied abzurufen!".to_string()).to_wrapper()))

View File

@ -21,7 +21,7 @@ pub fn create_user(settings: &State<Settings>, cookie: SessionCookie, create_use
let caller = parse_member_cookie(cookie.member)?;
let data = create_user_data.into_inner();
let member_groups = get_groups_for_member(settings, data.member_id);
let member_groups = get_groups_for_member(settings, data.member_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access_to_member_or_group(settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {

View File

@ -21,7 +21,7 @@ pub fn delete_user(settings: &State<Settings>, cookie: SessionCookie, user_id: S
None => return Err(Json(ApiError::new(404, "Nicht gefunden.".to_string()).to_wrapper()))
};
let member_groups = get_groups_for_member(settings, member.entity_id);
let member_groups = get_groups_for_member(settings, member.entity_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if caller.entity_id != member.entity_id { //Skip permission check if user edits own login
if !check_access_to_member_or_group(settings, member.entity_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {

View File

@ -28,7 +28,7 @@ pub fn update_user(settings: &State<Settings>, cookie: SessionCookie, user_id: S
return Err(Json(ApiError::new(400, "User id's doesn't match".to_string()).to_wrapper()))
}
let member_groups = get_groups_for_member(settings, data.member_id);
let member_groups = get_groups_for_member(settings, data.member_id).unwrap().into_iter().map(|m|crate::modules::member_management::model::groups::Group::from(m)).collect();
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access_to_member_or_group(settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {

View File

@ -9,7 +9,6 @@ use crate::database::model::member_licenses::MemberDrivePermission;
use diesel::result::Error;
use crate::database::controller::member_qualifications::check_qualification_for_member;
use uuid::Uuid;
use crate::modules::api::model::api_outcome::ApiErrorWrapper;
pub fn check_position_requirements(settings: &State<Settings>, position_id: uuid::Uuid, member_id: uuid::Uuid) -> Result<bool, RequirementParserError>{
let position = match get_eu_position(settings, position_id){

View File

@ -1,3 +1,4 @@
use crate::database::controller::groups::GroupMembership;
use crate::database::model::groups::GroupMemberCount;
use crate::database::model::roles::Role;
use crate::helper::sitebuilder::model::general::{Footer, Header};
@ -37,3 +38,14 @@ pub struct GroupUpdateData {
pub(crate) group_name: String,
pub(crate) group_description: Option<String>,
}
impl From<GroupMembership> for Group {
fn from(gm: GroupMembership) -> Self {
Group{
entity_id: gm.group_id,
group_name: gm.group_name,
group_description: gm.group_description,
selected: false
}
}
}

View File

@ -4,7 +4,6 @@ use rocket::http::Status;
use rocket::State;
use rocket_dyn_templates::Template;
use crate::helper::sitebuilder::model::general::{Header, Stylesheet, Footer, Script};
use crate::helper::sitebuilder::model::sidebar::Sidebar;
#[derive(Serialize)]
pub struct PrintEventNote{