Skip to content

Commit

Permalink
✨ : organization creation & deletion
Browse files Browse the repository at this point in the history
resolves #107
  • Loading branch information
juwit committed Dec 28, 2020
1 parent d7f1ed4 commit 8c5a778
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 17 deletions.
89 changes: 89 additions & 0 deletions src/main/client/app/pages/users/organizations.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<div>
<b-modal
id="new-organization-modal"
title="New Organization"
ok-title="Create"
auto-focus-button="ok"
@ok="createOrg"
@cancel="newOrganizationName = ''"
@close="newOrganizationName = ''"
>
<b-input
v-model="newOrganizationName"
autofocus
/>
</b-modal>

<b-button
v-b-modal.new-organization-modal
variant="primary"
>
<font-awesome-icon
icon="plus"
class="icon"
/>
Create new organization
</b-button>

<div class="block mt-3">
<b-table
:items="teams"
:fields="fields"
striped
fixed
outlined
>
<template v-slot:cell(edit)="row">
<b-button
variant="outline-danger"
class="ml-1"
@click="deleteOrg(row.item)"
>
<font-awesome-icon
:icon="['far', 'trash-alt']"
class="icon"
/>
</b-button>
</template>
</b-table>
</div>
</div>
</template>

<script>
import { createOrganization, deleteOrganization, getTeams } from '@/shared/api/teams-api';
import { displayNotification } from '@/shared/services/modal-service';
export default {
name: 'AppOrganizations',
data: () => ({
teams: [],
newOrganizationName: '',
fields: [
{ key: 'id', label: 'Name', sortable: true },
{ key: 'edit' },
],
}),
async created() {
await this.refresh();
},
methods: {
async refresh() {
this.teams = await getTeams();
},
async createOrg() {
await createOrganization({ id: this.newOrganizationName })
.then(() => displayNotification(this, { message: 'Organization created', variant: 'success' }))
.then(this.refresh)
.catch(({ error, message }) => displayNotification(this, { title: error, message, variant: 'danger' }));
},
async deleteOrg(organization) {
await deleteOrganization(organization.id)
.then(() => displayNotification(this, { message: 'Organization deleted', variant: 'success' }))
.then(this.refresh)
.catch(({ error, message }) => displayNotification(this, { title: error, message, variant: 'danger' }));
},
},
};
</script>
13 changes: 12 additions & 1 deletion src/main/client/app/pages/users/users-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,25 @@ const usersRoutes = [
{
path: '/users/:username',
name: 'user_edition',
component: () => import(/* webpackChunkName: "chunk-stacks" */ '@/pages/users/user-edit.vue'),
component: () => import(/* webpackChunkName: "chunk-users" */ '@/pages/users/user-edit.vue'),
meta: {
authorities: ['ROLE_ADMIN'],
breadcrumb: [{ text: 'Users', to: { name: 'users' } }, { text: 'User edition' }],
title: 'Gaia - Edit User',
},
props: true,
},
{
path: '/organizations',
name: 'organizations',
component: () => import(/* webpackChunkName: "chunk-users" */ '@/pages/users/organizations.vue'),
meta: {
authorities: ['ROLE_ADMIN'],
breadcrumb: [{ text: 'Organizations' }],
title: 'Gaia - Organization',
},
props: true,
},
];

export default usersRoutes;
4 changes: 4 additions & 0 deletions src/main/client/app/shared/api/teams-api.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import axios from 'axios';

export const getTeams = async () => axios.get('/api/teams');

export const deleteOrganization = async (organizationId) => axios.delete(`/api/teams/${organizationId}`);

export const createOrganization = async (organization) => axios.post('/api/teams', organization);
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
{
route: 'users', icon: 'user-friends', class: 'yellow_color', title: 'Users', roles: ['ROLE_ADMIN'],
},
{
route: 'organizations', icon: 'sitemap', class: 'yellow_color', title: 'Organizations', roles: ['ROLE_ADMIN'],
},
],
}),
computed: {
Expand Down
2 changes: 2 additions & 0 deletions src/main/client/app/shared/config/fontawesome-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
faRocket,
faSave,
faSignOutAlt,
faSitemap,
faStarOfLife,
faStop,
faStopCircle,
Expand Down Expand Up @@ -90,6 +91,7 @@ export default {
faRocket,
faCaretSquareUp,
faSave,
faSitemap,
faStopCircle,
faUpload,
faUserShield,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

import io.gaia_app.teams.Team;
import io.gaia_app.teams.repository.TeamRepository;
import io.gaia_app.teams.repository.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand All @@ -26,4 +24,16 @@ public List<Team> teams(){
return this.teamRepository.findAll();
}

@PostMapping
@Secured("ROLE_ADMIN")
public Team createOrganization(@RequestBody Team organization){
return this.teamRepository.save(organization);
}

@DeleteMapping("/{organizationId}")
@Secured("ROLE_ADMIN")
public void deleteOrganization(@PathVariable String organizationId){
this.teamRepository.deleteById(organizationId);
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package io.gaia_app.teams.controller;

import io.gaia_app.teams.Team;
import io.gaia_app.teams.repository.TeamRepository;
import io.gaia_app.test.SharedMongoContainerTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

Expand All @@ -28,6 +34,9 @@ class TeamsRestControllerIT extends SharedMongoContainerTest {
@Autowired
private TeamsRestController teamsRestController;

@Autowired
private TeamRepository teamRepository;

@Autowired
private MockMvc mockMvc;

Expand All @@ -39,22 +48,62 @@ void setUp() {
}

@Test
@WithMockUser("Mary J")
void teams_shouldBeAccessible_forStandardUsers() {
Assertions.assertDoesNotThrow(() -> teamsRestController.teams());
void teams_shouldBeExposed_atSpecificUrl() throws Exception {
mockMvc.perform(get("/api/teams"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(3)))
.andExpect(jsonPath("$..id", contains("Ze Team", "Not Ze Team", "Sith")));
}

@Test
void teams_shouldBeAccessible_forAdminUser() {
Assertions.assertDoesNotThrow(() -> teamsRestController.teams());
void createOrganization_shouldCreateOrganization() throws Exception {
mockMvc.perform(post("/api/teams")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"id\":\"Gungans\"}"))
.andExpect(status().isOk());

assertTrue(teamRepository.existsById("Gungans"));
}

@Test
void teams_shouldBeExposed_atSpecificUrl() throws Exception {
mockMvc.perform(get("/api/teams"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(3)))
.andExpect(jsonPath("$..id", contains("Ze Team", "Not Ze Team", "Sith")));
void deleteOrganization_shouldDeleteOrganization() throws Exception {
teamRepository.save(new Team("First Order"));

mockMvc.perform(delete("/api/teams/First Order")
.with(csrf()))
.andExpect(status().isOk());

assertFalse(teamRepository.existsById("First Order"));
}

@Nested
class AccessControl {

@Test
@WithMockUser("Jar Jar Binks")
void teams_shouldBeAccessible_forStandardUsers() {
Assertions.assertDoesNotThrow(() -> teamsRestController.teams());
}

@Test
@WithMockUser("Jar Jar Binks")
void createOrganization_shouldBeForbidden_forStandardUsers() {
assertThrows(AccessDeniedException.class, () -> teamsRestController.createOrganization(new Team("Gungans")));
}

@Test
@WithMockUser("Jar Jar Binks")
void deleteOrganization_shouldBeForbidden_forStandardUsers() {
assertThrows(AccessDeniedException.class, () -> teamsRestController.deleteOrganization("Gungans"));
}

@Test
@WithMockUser(value = "admin", roles = "ADMIN")
void teams_shouldBeAccessible_forAdminUser() {
Assertions.assertDoesNotThrow(() -> teamsRestController.teams());
}

}

}

0 comments on commit 8c5a778

Please sign in to comment.