Skip to content

Commit

Permalink
Merge pull request #155 from MathisBurger/feature/verified-groups
Browse files Browse the repository at this point in the history
Verified groups
  • Loading branch information
MathisBurger authored Nov 18, 2024
2 parents fb60bda + f68cd7c commit a0a45c5
Show file tree
Hide file tree
Showing 19 changed files with 180 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE groups DROP COLUMN verified;
1 change: 1 addition & 0 deletions tasky/migrations/2024-11-18-175123_verified_groups/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE groups ADD COLUMN verified BOOLEAN NOT NULL DEFAULT false;
32 changes: 26 additions & 6 deletions tasky/src/models/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct Group {
pub join_policy: JoinRequestPolicy,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
pub verified: bool,
}

/// Used to create a group in database
Expand Down Expand Up @@ -104,15 +105,25 @@ impl GroupRepository {
page: i64,
conn: &mut DB,
) -> PaginatedModel<Group> {
dsl::groups
let result = dsl::groups
.filter(
dsl::tutor
.eq(member_id)
.or(dsl::members.contains(vec![Some(member_id)])),
)
.group_by((dsl::id, dsl::verified))
.order(dsl::verified.desc())
.paginate(page)
.load_and_count_pages::<Group>(conn)
.expect("Cannot fetch groups for member")
.load_and_count_pages::<Group>(conn);
if let Ok(model) = result {
model
} else {
PaginatedModel {
results: vec![],
total: 0,
page,
}
}
}

/// Gets all groups a user is member or tutor of
Expand All @@ -139,16 +150,25 @@ impl GroupRepository {
.expect("Result cannot be fetched");

let results = dsl::groups
.group_by((dsl::id, dsl::verified))
.into_boxed()
.filter(apply_search_filter(member_id, requested, search))
.order(dsl::verified.desc())
.limit(50)
.offset((page - 1) * 50)
.load::<Group>(conn)
.expect("Result cannot be fetched");
.load::<Group>(conn);

if results.is_err() {
return PaginatedModel {
total: 0,
results: vec![],
page,
};
}

PaginatedModel {
total,
results,
results: results.unwrap(),
page,
}
}
Expand Down
4 changes: 4 additions & 0 deletions tasky/src/response/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct GroupResponse {
pub tutor: User,
pub request_count: i32,
pub join_policy: JoinRequestPolicy,
pub verified: bool,
}

/// The minified group response
Expand All @@ -28,6 +29,7 @@ pub struct MinifiedGroupResponse {
pub member_count: i32,
pub tutor: User,
pub join_policy: JoinRequestPolicy,
pub verified: bool,
}

/// The groups response
Expand Down Expand Up @@ -56,6 +58,7 @@ impl Enrich<Group> for MinifiedGroupResponse {
member_count: from.members.len() as i32,
tutor: tut.into_inner().into(),
join_policy: from.join_policy.clone(),
verified: from.verified,
})
}
}
Expand Down Expand Up @@ -114,6 +117,7 @@ impl Enrich<Group> for GroupResponse {
.collect(),
tutor: tut.into_inner().into(),
join_policy: from.join_policy.clone(),
verified: from.verified,
request_count,
})
}
Expand Down
48 changes: 48 additions & 0 deletions tasky/src/routes/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,51 @@ pub async fn delete_group(

Ok(HttpResponse::Ok().finish())
}

/// Endpoint to verify a group
#[post("/groups/{id}/verify")]
pub async fn verify_group(
data: web::Data<AppState>,
user: web::ReqData<UserData>,
path: web::Path<(i32,)>,
) -> Result<HttpResponse, ApiError> {
let conn = &mut data.db.db.get().unwrap();
let path_data = path.into_inner();

let mut group = GroupRepository::get_by_id(path_data.0, conn).ok_or(ApiError::BadRequest {
message: "No access to group".to_string(),
})?;

if !StaticSecurity::is_granted(crate::security::StaticSecurityAction::IsAdmin, &user) {
return Err(ApiError::Forbidden {
message: "Only admins are allowed to verify groups".to_string(),
});
}
group.verified = true;
GroupRepository::update_group(group, conn);
Ok(HttpResponse::Ok().finish())
}

/// Endpoint to unverify a group
#[post("/groups/{id}/unverify")]
pub async fn unverify_group(
data: web::Data<AppState>,
user: web::ReqData<UserData>,
path: web::Path<(i32,)>,
) -> Result<HttpResponse, ApiError> {
let conn = &mut data.db.db.get().unwrap();
let path_data = path.into_inner();

let mut group = GroupRepository::get_by_id(path_data.0, conn).ok_or(ApiError::BadRequest {
message: "No access to group".to_string(),
})?;

if !StaticSecurity::is_granted(crate::security::StaticSecurityAction::IsAdmin, &user) {
return Err(ApiError::Forbidden {
message: "Only admins are allowed to unverify groups".to_string(),
});
}
group.verified = false;
GroupRepository::update_group(group, conn);
Ok(HttpResponse::Ok().finish())
}
2 changes: 2 additions & 0 deletions tasky/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub fn init_services(cfg: &mut web::ServiceConfig) {
.service(group::remove_user)
.service(group::leave_group)
.service(group::delete_group)
.service(group::verify_group)
.service(group::unverify_group)
.service(group_join_request::create_join_request)
.service(group_join_request::get_join_requests)
.service(group_join_request::approve_join_request)
Expand Down
1 change: 1 addition & 0 deletions tasky/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ diesel::table! {
join_policy -> JoinRequestPolicy,
created_at -> Timestamp,
updated_at -> Timestamp,
verified -> Bool,
}
}

Expand Down
14 changes: 14 additions & 0 deletions tasky/tests/security/group_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ fn test_create_group() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Create, &admin), false);
}
Expand All @@ -41,6 +42,7 @@ fn test_read_group_as_admin() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Read, &admin), true);
}
Expand All @@ -58,6 +60,7 @@ fn test_read_group_as_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Read, &admin), true);
}
Expand All @@ -75,6 +78,7 @@ fn test_read_group_as_wrong_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Read, &admin), false);
}
Expand All @@ -92,6 +96,7 @@ fn test_read_group_as_student() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Read, &admin), true);
}
Expand All @@ -109,6 +114,7 @@ fn test_read_group_as_wrong_student() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Read, &admin), false);
}
Expand All @@ -126,6 +132,7 @@ fn test_update_as_admin() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Update, &user), true);
}
Expand All @@ -143,6 +150,7 @@ fn test_update_as_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Update, &user), true);
}
Expand All @@ -160,6 +168,7 @@ fn test_update_as_wrong_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Update, &user), false);
}
Expand All @@ -177,6 +186,7 @@ fn test_update_as_student() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Update, &user), false);
}
Expand All @@ -194,6 +204,7 @@ fn test_delete_as_admin() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Delete, &user), false);
}
Expand All @@ -211,6 +222,7 @@ fn test_delete_as_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Delete, &user), false);
}
Expand All @@ -228,6 +240,7 @@ fn test_delete_as_wrong_tutor() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Delete, &user), false);
}
Expand All @@ -245,6 +258,7 @@ fn test_delete_as_student() {
.unwrap(),
updated_at: NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
.unwrap(),
verified: false,
};
assert_eq!(group.is_granted(SecurityAction::Delete, &user), false);
}
Expand Down
13 changes: 5 additions & 8 deletions web/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,16 @@ const DashboardPage = () => {
</Grid.Col>
<Grid.Col span={8}>
<Card shadow="sm" padding="xl" mt={20}>
<Title order={2}>Release v0.2.1</Title>
<Title order={2}>Release v0.2.2</Title>
<Text>
We had some groundbreaking changes within our app for the current
release:
<br />
- JavaFX support <br/>
- Verified groups <br/>
- Group leaving and deletion <br/>
- Group join policy feature update <br/>
- Notification system <br/>
- Stage3 Spotlight <br/>
- Assignment test editing <br/>
- Completion indicator on assignments <br/>
- Some more backlinks <br/>
- Big performance improvements for web <br/>
- Minor bug fixes
- Convert to tutor account <br/>
</Text>
</Card>
</Grid.Col>
Expand Down
20 changes: 20 additions & 0 deletions web/app/groups/[groupId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {isGranted} from "@/service/auth";
import {UserRoles} from "@/service/types/usernator";
import LeaveGroupModal from "@/components/group/LeaveGroupModal";
import DeleteGroupModal from "@/components/group/DeleteGroupModal";
import VerifiedBadge from "@/components/VerifiedBadge";
import NavigateBack from "@/components/NavigateBack";

const GroupDetailsPage = ({ params }: { params: { groupId: string } }) => {
const id = parseInt(`${params.groupId}`, 10);
Expand All @@ -27,6 +29,17 @@ const GroupDetailsPage = ({ params }: { params: { groupId: string } }) => {
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
const { t } = useTranslation("common");

const changeVerifiedState = async () => {
if (group) {
if (group.verified) {
await api.unverify(group.id);
} else {
await api.verify(group.id);
}
refetch();
}
}

useEffect(() => {
if (group) {
addGroup(group);
Expand All @@ -43,12 +56,16 @@ const GroupDetailsPage = ({ params }: { params: { groupId: string } }) => {

return (
<Container fluid>
<NavigateBack />
<Group>
<Title>{group?.title ?? "Loading"}</Title>
<Badge>{group?.tutor?.username ?? "Loading"}</Badge>
{group?.join_policy && (
<GroupJoinPolicyBadge policy={group.join_policy} />
)}
{group?.verified && (
<VerifiedBadge />
)}
{(isGranted(user, [UserRoles.Admin]) || group?.tutor.id === user?.id) && (
<>
<Button onClick={() => setUpdateModalOpen(true)}>{t('common:titles.update-group')}</Button>
Expand All @@ -58,6 +75,9 @@ const GroupDetailsPage = ({ params }: { params: { groupId: string } }) => {
{isGranted(user, [UserRoles.Student]) && (
<Button color="red" onClick={() => setLeaveModalOpen(true)}>{t('group:actions.leave')}</Button>
)}
{isGranted(user, [UserRoles.Admin]) && (
<Button color="cyan" onClick={changeVerifiedState}>{group?.verified ? t('group:actions.unverify') : t('group:actions.verify')}</Button>
)}
</Group>
{group === null ? (
<CentralLoading />
Expand Down
4 changes: 4 additions & 0 deletions web/app/groups/displayComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import { isGranted } from "@/service/auth";
import { useTranslation } from "react-i18next";
import GroupJoinPolicyBadge from "@/components/group/GroupJoinPolicyBadge";
import VerifiedBadge from "@/components/VerifiedBadge";

interface DisplayComponentProps {
groups: MinifiedGroup[];
Expand All @@ -34,6 +35,9 @@ const GroupsDisplayComponent = ({
{
field: "title",
label: t("group:cols.title"),
render: (title, row) => (
<p>{title}&nbsp;{row.verified ? <VerifiedBadge /> : null} </p>
)
},
{
field: "member_count",
Expand Down
Loading

0 comments on commit a0a45c5

Please sign in to comment.