Skip to content
Snippets Groups Projects
Commit fa21064a authored by Martin Weise's avatar Martin Weise
Browse files

Added load test to verify API, fixed the database service

parent 1d92a561
No related branches found
No related tags found
2 merge requests!81New stable release,!80Multiple features connected with user management and ownership of databases
Showing
with 485 additions and 1052 deletions
......@@ -43,4 +43,5 @@ from api_authentication.models.user_forgot_dto import UserForgotDto
from api_authentication.models.user_password_dto import UserPasswordDto
from api_authentication.models.user_reset_dto import UserResetDto
from api_authentication.models.user_roles_dto import UserRolesDto
from api_authentication.models.user_theme_set_dto import UserThemeSetDto
from api_authentication.models.user_update_dto import UserUpdateDto
......@@ -920,3 +920,108 @@ class UserEndpointApi(object):
_preload_content=params.get('_preload_content', True),
_request_timeout=params.get('_request_timeout'),
collection_formats=collection_formats)
def update_theme(self, body, id, **kwargs): # noqa: E501
"""Update user theme # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.update_theme(body, id, async_req=True)
>>> result = thread.get()
:param async_req bool
:param UserThemeSetDto body: (required)
:param int id: (required)
:return: None
If the method is called asynchronously,
returns the request thread.
"""
kwargs['_return_http_data_only'] = True
if kwargs.get('async_req'):
return self.update_theme_with_http_info(body, id, **kwargs) # noqa: E501
else:
(data) = self.update_theme_with_http_info(body, id, **kwargs) # noqa: E501
return data
def update_theme_with_http_info(self, body, id, **kwargs): # noqa: E501
"""Update user theme # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.update_theme_with_http_info(body, id, async_req=True)
>>> result = thread.get()
:param async_req bool
:param UserThemeSetDto body: (required)
:param int id: (required)
:return: None
If the method is called asynchronously,
returns the request thread.
"""
all_params = ['body', 'id'] # noqa: E501
all_params.append('async_req')
all_params.append('_return_http_data_only')
all_params.append('_preload_content')
all_params.append('_request_timeout')
params = locals()
for key, val in six.iteritems(params['kwargs']):
if key not in all_params:
raise TypeError(
"Got an unexpected keyword argument '%s'"
" to method update_theme" % key
)
params[key] = val
del params['kwargs']
# verify the required parameter 'body' is set
if ('body' not in params or
params['body'] is None):
raise ValueError("Missing the required parameter `body` when calling `update_theme`") # noqa: E501
# verify the required parameter 'id' is set
if ('id' not in params or
params['id'] is None):
raise ValueError("Missing the required parameter `id` when calling `update_theme`") # noqa: E501
collection_formats = {}
path_params = {}
if 'id' in params:
path_params['id'] = params['id'] # noqa: E501
query_params = []
header_params = {}
form_params = []
local_var_files = {}
body_params = None
if 'body' in params:
body_params = params['body']
# HTTP header `Accept`
header_params['Accept'] = self.api_client.select_header_accept(
['*/*']) # noqa: E501
# HTTP header `Content-Type`
header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
['application/json']) # noqa: E501
# Authentication setting
auth_settings = ['bearerAuth'] # noqa: E501
return self.api_client.call_api(
'/api/user/{id}/theme', 'PUT',
path_params,
query_params,
header_params,
body=body_params,
post_params=form_params,
files=local_var_files,
response_type=None, # noqa: E501
auth_settings=auth_settings,
async_req=params.get('async_req'),
_return_http_data_only=params.get('_return_http_data_only'),
_preload_content=params.get('_preload_content', True),
_request_timeout=params.get('_request_timeout'),
collection_formats=collection_formats)
......@@ -35,4 +35,5 @@ from api_authentication.models.user_forgot_dto import UserForgotDto
from api_authentication.models.user_password_dto import UserPasswordDto
from api_authentication.models.user_reset_dto import UserResetDto
from api_authentication.models.user_roles_dto import UserRolesDto
from api_authentication.models.user_theme_set_dto import UserThemeSetDto
from api_authentication.models.user_update_dto import UserUpdateDto
......@@ -259,7 +259,7 @@ class TableDataEndpointApi(object):
auth_settings = ['bearerAuth'] # noqa: E501
return self.api_client.call_api(
'/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET',
'/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD',
path_params,
query_params,
header_params,
......@@ -380,7 +380,7 @@ class TableDataEndpointApi(object):
auth_settings = ['bearerAuth'] # noqa: E501
return self.api_client.call_api(
'/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'HEAD',
'/api/container/{id}/database/{databaseId}/table/{tableId}/data', 'GET',
path_params,
query_params,
header_params,
......
......@@ -24,7 +24,6 @@ from api_table.models.api_error_dto import ApiErrorDto
from api_table.models.column_create_dto import ColumnCreateDto
from api_table.models.column_dto import ColumnDto
from api_table.models.concept_dto import ConceptDto
from api_table.models.granted_authority_dto import GrantedAuthorityDto
from api_table.models.image_date_dto import ImageDateDto
from api_table.models.table_brief_dto import TableBriefDto
from api_table.models.table_create_dto import TableCreateDto
......
......@@ -18,7 +18,6 @@ from api_table.models.api_error_dto import ApiErrorDto
from api_table.models.column_create_dto import ColumnCreateDto
from api_table.models.column_dto import ColumnDto
from api_table.models.concept_dto import ConceptDto
from api_table.models.granted_authority_dto import GrantedAuthorityDto
from api_table.models.image_date_dto import ImageDateDto
from api_table.models.table_brief_dto import TableBriefDto
from api_table.models.table_create_dto import TableCreateDto
......
#!/bin/env python3
import time
import os
import shutil
import uuid
import sys
import api_query.rest
from api_authentication.api.authentication_endpoint_api import AuthenticationEndpointApi
from api_authentication.api.user_endpoint_api import UserEndpointApi
from api_container.api.container_endpoint_api import ContainerEndpointApi
from api_database.api.container_database_endpoint_api import ContainerDatabaseEndpointApi
from api_table.api.table_endpoint_api import TableEndpointApi
from api_query.api.table_data_endpoint_api import TableDataEndpointApi
from api_query.api.query_endpoint_api import QueryEndpointApi
from api_identifier.api.identifier_endpoint_api import IdentifierEndpointApi
from api_identifier.api.persistence_endpoint_api import PersistenceEndpointApi
authentication = AuthenticationEndpointApi()
user = UserEndpointApi()
container = ContainerEndpointApi()
database = ContainerDatabaseEndpointApi()
table = TableEndpointApi()
query = QueryEndpointApi()
data = TableDataEndpointApi()
identifier = IdentifierEndpointApi()
persistence = PersistenceEndpointApi()
token = ""
def create_user(username):
response = user.register({
"username": username,
"password": username,
"email": username + "@gmail.com"
})
print("created user")
return response
def auth_user(username):
response = authentication.authenticate_user1({
"username": username,
"password": username
})
print("authenticated user")
token = response.token
container.api_client.default_headers = {"Authorization": "Bearer " + token}
database.api_client.default_headers = {"Authorization": "Bearer " + token}
table.api_client.default_headers = {"Authorization": "Bearer " + token}
data.api_client.default_headers = {"Authorization": "Bearer " + token}
query.api_client.default_headers = {"Authorization": "Bearer " + token}
identifier.api_client.default_headers = {"Authorization": "Bearer " + token}
user.api_client.default_headers = {"Authorization": "Bearer " + token}
persistence.api_client.default_headers = {"Authorization": "Bearer " + token}
return response
def create_container():
response = container.create1({
"name": "Airquality " + str(uuid.uuid1()),
"repository": "mariadb",
"tag": "10.5"
})
print("created container")
return response
def start_container(container_id):
response = container.modify({
"action": "start"
}, container_id)
time.sleep(5)
print("started container")
return response
def create_database(container_id, is_public=True):
response = database.create({
"name": "Airquality " + str(uuid.uuid1()),
"description": "Hourly measurements in Zürich, Switzerland",
"is_public": is_public
}, container_id)
print("created database")
return response
def update_database(container_id, database_id, is_public=True):
response = database.update({
"description": "This dataset includes daily values from 1983 to the current day, divided into annual files. This includes the maximum hourly average and the number of times the hourly average limit value for ozone was exceeded and the daily averages for sulfur dioxide (SO2), carbon monoxide (CO), nitrogen oxide (NOx), nitrogen monoxide (NO), nitrogen dioxide (NO2), particulate matter (PM10 and PM2.5). ) and particle number (PN), provided that they are of sufficient quality. The values of the completed day for the current year are updated every 30 minutes after midnight (UTC+1).",
"publisher": "Technical University of Vienna",
"license": {
"identifier": "CC0-1.0",
"uri": "https://creativecommons.org/publicdomain/zero/1.0/legalcode"
},
"language": "en",
"is_public": is_public,
"publication": "2022-07-19"
}, container_id, database_id)
print("updated database")
return response
def create_table(container_id, database_id, columns=None):
if columns is None:
columns = [{
"name": "Date",
"type": "date",
"dfid": 1,
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Location",
"type": "string",
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Parameter",
"type": "string",
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Interval",
"type": "string",
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Unit",
"type": "string",
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Value",
"type": "decimal",
"unique": False,
"primary_key": False,
"null_allowed": True,
}, {
"name": "Status",
"type": "string",
"unique": False,
"primary_key": False,
"null_allowed": True,
}]
response = table.create({
"name": "Airquality " + str(uuid.uuid1()),
"description": "Airquality in Zürich, Switzerland",
"columns": columns
}, container_id, database_id)
print("created table")
return response
def find_table(container_id, database_id, table_id):
response = table.find_by_id(container_id, database_id, table_id)
print("found table")
return response
def fill_table(container_id, database_id, table_id):
shutil.copyfile(os.getcwd() + "/resources/ugz_ogd_air_h1_2021.csv", "/tmp/ugz_ogd_air_h1_2021.csv")
response = data.import_csv({
"location": "/tmp/ugz_ogd_air_h1_2021.csv",
"separator": ",",
"quote": "\"",
"skip_lines": 1
}, container_id, database_id, table_id)
print("filled table")
return response
def create_query(container_id, database_id, statement, page=0, size=3):
try:
response = query.execute({
"statement": statement
}, container_id, database_id, page=page, size=size)
print("executed query")
return response
except api_query.rest.ApiException as e:
print(e)
def create_identifier(container_id, database_id, query_id, visibility="everyone"):
response = identifier.create({
"qid": query_id,
"title": "Airquality",
"description": "Subset used for a scientific article",
"visibility": visibility,
"creators": [{
"name": "Weise, Martin",
"affiliation": "TU Wien",
"orcid": "0000-0003-4216-302X"
}, {
"name": "Rauber, Andreas",
"affiliation": "TU Wien",
"orcid": "0000-0002-9272-6225"
}],
"publication": "2022-07-16",
"related_identifiers": [{
"value": "http://localhost:3000/container/" + str(container_id) + "/database/" + str(database_id),
"type": "URL",
"relation": "IsCitedBy"
}]
}, token, container_id, database_id)
print("created identifier")
return response
if __name__ == '__main__':
#
# create 1 user and 3 containers (public, private, public)
#
create_user("test1")
auth_user("test1")
# container 1
cid = create_container().id
start_container(cid)
dbid = create_database(cid).id
update_database(cid, dbid)
tid = create_table(cid, dbid).id
tname = find_table(cid, dbid, tid).internal_name
fill_table(cid, dbid, tid)
create_query(cid, dbid, "select `id` from `" + tname + "`")
create_query(cid, dbid, "select `date` from `" + tname + "`")
qid = create_query(cid, dbid, "select `date`, `location`, `status` from `" + tname + "`").id
create_query(cid, dbid, "select `foo` from `" + tname + "`")
create_identifier(cid, dbid, qid)
# container 2 (=private)
cid = create_container().id
start_container(cid)
dbid = create_database(cid, False).id
update_database(cid, dbid, is_public=False)
tid = create_table(cid, dbid).id
tname = find_table(cid, dbid, tid).internal_name
fill_table(cid, dbid, tid)
qid = create_query(cid, dbid, "select `id` from `" + tname + "`").id
create_identifier(cid, dbid, qid, visibility="self")
qid = create_query(cid, dbid, "select `id` from `" + tname + "`").id
create_identifier(cid, dbid, qid)
# container 3 with 3 tables
cid = create_container().id
start_container(cid)
dbid = create_database(cid).id
update_database(cid, dbid)
create_table(cid, dbid, columns=[])
create_table(cid, dbid, columns=[{
"name": "primary",
"type": "string",
"unique": True,
"primary_key": True,
"null_allowed": False,
}])
create_table(cid, dbid, columns=[{
"name": "primary",
"type": "number",
"unique": True,
"primary_key": True,
"null_allowed": False,
}])
create_table(cid, dbid, columns=[{
"name": "primary",
"type": "date",
"unique": True,
"primary_key": True,
"null_allowed": False,
}])
This diff is collapsed.
......@@ -155,6 +155,17 @@ public class UserEndpoint {
.body(userMapper.userToUserDto(entity));
}
@PutMapping("/{id}/theme")
@Transactional
@PreAuthorize("hasPermission(#id, 'UPDATE_THEME')")
@Operation(summary = "Update user theme", security = @SecurityRequirement(name = "bearerAuth"))
public ResponseEntity<Void> updateTheme(@NotNull @PathVariable("id") Long id,
@NotNull @Valid @RequestBody UserThemeSetDto data) throws UserNotFoundException {
userService.updateTheme(id, data);
return ResponseEntity.accepted()
.build();
}
@PutMapping("/{id}/password")
@Transactional
@PreAuthorize("hasRole('ROLE_DEVELOPER') or hasPermission(#id, 'UPDATE_PASSWORD')")
......
......@@ -88,6 +88,15 @@ public interface UserService {
User updateRoles(Long id, UserRolesDto data)
throws UserNotFoundException, RoleNotFoundException, RoleUniqueException;
/**
* Sets the theme for the provided user.
*
* @param id The user id.
* @param data The theme.
* @throws UserNotFoundException The user was not found.
*/
void updateTheme(Long id, UserThemeSetDto data) throws UserNotFoundException;
/**
* Updates a user with the given id and updated password.
*
......
......@@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.ConstraintViolationException;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
......@@ -212,6 +211,18 @@ public class UserServiceImpl implements UserService {
return entity;
}
@Override
@Transactional
public void updateTheme(Long id, UserThemeSetDto data) throws UserNotFoundException {
/* check */
final User user = find(id);
/* save */
user.setThemeDark(data.getThemeDark());
final User entity = userRepository.save(user);
log.info("Updated user with id {}", entity.getId());
log.debug("updated user {}", entity);
}
@Override
@Transactional
public User updatePassword(Long id, UserPasswordDto data) throws UserNotFoundException {
......
......@@ -21,6 +21,5 @@ public interface QueryServiceGateway {
* @throws RemoteUnavailableException The remote service is not available.
*/
QueryDto find(Long containerId, Long databaseId, IdentifierCreateDto identifier, String authorization)
throws QueryNotFoundException,
RemoteUnavailableException;
throws QueryNotFoundException, RemoteUnavailableException;
}
......@@ -126,6 +126,7 @@ public class IdentifierServiceImpl implements IdentifierService {
relatedIdentifierRepository.save(id);
});
}
entity.setQueryNormalized(query.getQueryNormalized());
final Identifier identifier = identifierRepository.save(entity);
log.info("Created identifier with id {}", identifier.getId());
log.debug("created identifier {}", identifier);
......
package at.tuwien.api.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.util.List;
@Getter
@Setter
......@@ -20,10 +18,6 @@ public class UserBriefDto {
@Parameter(name = "id")
private Long id;
@ToString.Exclude
@Parameter(name = "user authorities")
private List<GrantedAuthorityDto> authorities;
@NotNull
@Parameter(name = "user name")
private String username;
......@@ -53,12 +47,4 @@ public class UserBriefDto {
@Parameter(name = "theme dark")
private Boolean themeDark;
@NotNull
@Parameter(name = "mail address")
private String email;
@JsonProperty("email_verified")
@Parameter(name = "mail address verified")
private Boolean emailVerified;
}
package at.tuwien.api.user;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserThemeSetDto {
@NotNull
@JsonProperty("theme_dark")
@Parameter(name = "theme dark")
private Boolean themeDark;
}
......@@ -37,9 +37,4 @@ public class UserUpdateDto {
@Parameter(name = "orcid")
private String orcid;
@NotNull
@JsonProperty("theme_dark")
@Parameter(name = "theme dark")
private Boolean themeDark;
}
......@@ -41,7 +41,7 @@ public abstract class AbstractEndpoint {
return true;
}
/* view-only operations are allowed on public databases */
if (database.getIsPublic() && List.of("DATA_EXPORT", "DATA_VIEW", "DATA_HISTORY").contains(permissionCode)) {
if (database.getIsPublic() && List.of("DATA_EXPORT", "DATA_VIEW", "DATA_HISTORY", "QUERY_VIEW_ALL").contains(permissionCode)) {
log.debug("grant permission {} because database is public", permissionCode);
return true;
}
......@@ -76,7 +76,7 @@ public abstract class AbstractEndpoint {
return true;
}
/* view-only operations are allowed on public databases */
if (database.getIsPublic() && List.of("QUERY_VIEW_ALL", "QUERY_VIEW", "QUERY_EXPORT").contains(
if (database.getIsPublic() && List.of("QUERY_VIEW_ALL", "QUERY_VIEW", "QUERY_EXPORT", "QUERY_RE_EXECUTE").contains(
permissionCode)) {
log.debug("grant permission {} because database is public", permissionCode);
return true;
......
......@@ -165,6 +165,7 @@ public interface StoreMapper {
.lastModified(data.getTimestamp(7) != null ? data.getTimestamp(7)
.toInstant() : null)
.query(data.getString(8))
.queryNormalized(data.getString(8))
.queryHash(data.getString(9))
.resultHash(data.getString(10) != null ? data.getString(10) : null)
.resultNumber(data.getLong(11))
......
......@@ -177,9 +177,8 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
@Override
@Transactional(readOnly = true)
public ExportResource findOne(Long containerId, Long databaseId, Long queryId)
throws DatabaseNotFoundException, ImageNotSupportedException,
ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException,
QueryMalformedException, DatabaseConnectionException {
throws DatabaseNotFoundException, ImageNotSupportedException, FileStorageException, QueryStoreException,
QueryNotFoundException, QueryMalformedException, DatabaseConnectionException {
final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv";
/* find */
final Database database = databaseService.find(containerId, databaseId);
......@@ -210,8 +209,8 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
@Override
@Transactional
public Long count(Long containerId, Long databaseId, Long tableId, Instant timestamp)
throws DatabaseNotFoundException, TableNotFoundException,
ImageNotSupportedException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException {
throws DatabaseNotFoundException, TableNotFoundException, ImageNotSupportedException,
DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException {
/* find */
final Database database = databaseService.find(containerId, databaseId);
final Table table = tableService.find(containerId, databaseId, tableId);
......
<template>
<v-app dark>
<v-app>
<v-navigation-drawer v-model="drawer" fixed app>
<v-img
contain
......@@ -104,6 +104,10 @@ export default {
data () {
return {
drawer: false,
user: {
theme_dark: null
},
loadingUser: true,
items: [
{
icon: mdiHome,
......@@ -157,9 +161,6 @@ export default {
username () {
return this.$store.state.user && this.$store.state.user.username
},
nextTheme () {
return this.$vuetify.theme.dark ? 'Light' : 'Dark'
},
container () {
return this.$store.state.container
},
......@@ -171,15 +172,29 @@ export default {
},
db () {
return this.$store.state.db
},
config () {
if (this.token === null) {
return {}
}
return {
headers: { Authorization: `Bearer ${this.token}` }
}
}
},
watch: {
$route () {
this.loadDB()
if (this.token) {
this.loadUser()
.then(() => this.setTheme())
}
}
},
mounted () {
this.loadDB()
this.loadUser()
.then(() => this.setTheme())
},
methods: {
logout () {
......@@ -197,6 +212,25 @@ export default {
console.error('Failed to load database', err)
}
}
},
async loadUser () {
if (!this.token) {
return
}
try {
this.loadingUser = true
const res = await this.$axios.put('/api/auth', {}, this.config)
console.debug('user data', res.data)
this.user = res.data
} catch (err) {
console.error('user data', err)
this.$toast.error('Failed to load user')
this.error = true
}
this.loadingUser = false
},
setTheme () {
this.$vuetify.theme.dark = this.user.theme_dark
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment