BBIO-46 WIP

This commit is contained in:
Markus Thielen 2023-11-01 11:44:19 +01:00
parent be1e94ee06
commit b387643068
11 changed files with 169 additions and 143 deletions

View File

@ -1,5 +1,5 @@
--
-- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-11-01 03:18:33+01:00
-- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-11-01 10:35:59+01:00
--
CREATE EXTENSION IF NOT EXISTS pgcrypto;
@ -23,6 +23,12 @@ CREATE TABLE "User" (
"name" VARCHAR
);
ALTER TABLE "List" ADD COLUMN ".ownerId" VARCHAR NOT NULL;
ALTER TABLE "Task" ADD COLUMN ".ownerId" VARCHAR NOT NULL;
ALTER TABLE "User" ADD COLUMN ".ownerId" VARCHAR NOT NULL;
ALTER TABLE "List" ADD PRIMARY KEY ("id");
ALTER TABLE "Task" ADD PRIMARY KEY ("id");
@ -37,9 +43,3 @@ ALTER TABLE "Task" ADD CONSTRAINT fk_task_3 FOREIGN KEY ("list_id") REFERENCES "
ALTER TABLE "User" ADD CONSTRAINT uq_user_4 UNIQUE (".ownerId");
ALTER TABLE "List" ADD COLUMN ".ownerId" VARCHAR NOT NULL;
ALTER TABLE "Task" ADD COLUMN ".ownerId" VARCHAR NOT NULL;
ALTER TABLE "User" ADD COLUMN ".ownerId" VARCHAR NOT NULL;

View File

@ -1,6 +1,31 @@
#
# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-11-01 03:18:33+01:00
# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-11-01 10:35:59+01:00
#
[resolvers._bb_user_User]
operation_name = "_bb_user_User"
[resolvers._bb_user_User.resolver.InternalQueryBuilder]
command_type = "SQLSelect"
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command]
table = "User"
command_type = "SQLSelect"
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns]]
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns.Column]
table = "User"
column = "username"
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.where_clauses]]
table = "User"
column = ".ownerId"
condition_str = "= $1"
index = ""
[resolvers.updateTask]
operation_name = "updateTask"
@ -36,45 +61,39 @@ column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.getUser]
operation_name = "getUser"
[resolvers.createTask]
operation_name = "createTask"
[resolvers.getUser.resolver.QueryBuilder]
command_type = "SQLSelect"
[resolvers.createTask.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.getUser.resolver.QueryBuilder.command]
table = "User"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.getUser.resolver.QueryBuilder.command.where_clauses]]
table = "User"
column = "username"
condition_str = "= '$username'"
index = ""
[resolvers.deleteTask]
operation_name = "deleteTask"
[resolvers.deleteTask.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteTask.resolver.QueryBuilder.command]
[resolvers.createTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.deleteTask.resolver.QueryBuilder.command.where_clauses]]
table = "Task"
column = "id"
condition_str = "= '$id'"
index = ""
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "description"
value = "'$description'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "completed"
value = "'$completed'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "list_id"
value = "'$list.$id'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.createUser]
operation_name = "createUser"
@ -98,6 +117,46 @@ value = "'$username'"
column = "name"
value = "'$name'"
[resolvers.deleteTask]
operation_name = "deleteTask"
[resolvers.deleteTask.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.deleteTask.resolver.QueryBuilder.command.where_clauses]]
table = "Task"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.getUser]
operation_name = "getUser"
[resolvers.getUser.resolver.QueryBuilder]
command_type = "SQLSelect"
[resolvers.getUser.resolver.QueryBuilder.command]
table = "User"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.getUser.resolver.QueryBuilder.command.where_clauses]]
table = "User"
column = "username"
condition_str = "= '$username'"
index = ""
[resolvers.createList]
operation_name = "createList"
@ -143,65 +202,6 @@ column = "id"
condition_str = "= '$id'"
index = ""
[resolvers._bb_user_User]
operation_name = "_bb_user_User"
[resolvers._bb_user_User.resolver.InternalQueryBuilder]
command_type = "SQLSelect"
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command]
table = "User"
command_type = "SQLSelect"
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns]]
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns.Column]
table = "User"
column = "username"
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.where_clauses]]
table = "User"
column = ".ownerId"
condition_str = "= $1"
index = ""
[resolvers.createTask]
operation_name = "createTask"
[resolvers.createTask.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "description"
value = "'$description'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "completed"
value = "'$completed'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "list_id"
value = "'$list.$id'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.deleteList]
operation_name = "deleteList"

View File

@ -3,13 +3,14 @@ import { RouterLink, RouterView } from 'vue-router'
import { store, loggedIn } from './store.js'
import {clearSession} from "./store.js";
import {Modal} from "bootstrap";
import { getOidcUserManager } from './util/oidc';
/**
* Logout the user.
*/
function logOut() {
clearSession();
location.href = "/";
getOidcUserManager().signoutRedirect();
}
/**
@ -48,7 +49,7 @@ function dismissError() {
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="navbar-text">Hello, {{ store.userDisplayName }}!</li>
<li class="nav-item ms-2">
<button class="nav-link btn btn-sm btn-secondary" @click="$oidc.signOut()">Logout</button>
<button class="nav-link btn btn-sm btn-secondary" @click="logOut()">Logout</button>
</li>
</ul>
</div>

View File

@ -53,7 +53,7 @@
</template>
<script>
import {gqlQuery} from "../util/net";
import { gqlRequest } from "../util/net";
import { showError, store } from "../store";
import { showModal } from "../util/misc";
@ -96,12 +96,17 @@ export default {
}`;
}
/* send query */
gqlQuery(
gqlRequest(
gql
).then(data => {
/* Save the list's id in case it was just created */
if (!list.id) {
if (list.id === NEW_LIST_ID) {
list.id = data.createList.id;
/* Update list id in data store */
const idx = store.lists.findIndex(item => item.id === NEW_LIST_ID);
if (idx !== -1) {
store.lists[idx].id = list.id;
}
}
}).catch(e => {
const errMsg = `Failed to save list: ${e}`;
@ -122,7 +127,7 @@ export default {
return;
}
/* List must be also deleted from the server */
gqlQuery(`mutation {
gqlRequest(`mutation {
deleteList(id: "${list.id}") {
id
}

View File

@ -73,8 +73,8 @@
</template>
<script>
import {gqlQuery} from "../util/net";
import {showError, store} from "../store";
import { gqlRequest } from "../util/net";
import { removeTask, showError, store } from "../store";
import { showModal } from "../util/misc";
const NEW_TASK_ID = "new-task-id";
@ -137,7 +137,7 @@ export default {
}
/* send query */
gqlQuery(
gqlRequest(
request
).then(data => {
/* Save the task's id in case it was just created */
@ -158,18 +158,24 @@ export default {
deleteTask(task) {
if (task.id !== NEW_TASK_ID) {
/* Task must be also deleted from the server */
gqlQuery(`mutation {
gqlRequest(`mutation {
deleteTask(id: "${task.id}") {
id
}
}`);
}`).then(data => {
console.info(`Deleted task '${task.title}' with id ${task.id}`);
/* Also remove task from the store */
removeTask(task);
}).catch(e => {
const errMsg = `Failed to delete task: ${e}`;
console.error(errMsg);
showError(errMsg);
});
} else {
/* Just delete task from the store */
removeTask(task);
}
/* delete task from the store. */
const idx = store.tasks.findIndex(item => item.id === task.id);
if (idx !== -1) {
store.tasks.splice(idx, 1);
}
},

View File

@ -1,12 +0,0 @@
<template>
</template>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
console.log('Component is mounted!');
});
</script>

View File

@ -5,7 +5,7 @@
* https://basebox.io
*/
import {loggedIn, showError, store} from "../store";
import {loggedIn, showError, storeInit } from "../store";
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { objectToQueryString } from "../util/net";
@ -39,7 +39,7 @@ router.beforeEach(async (to, from) => {
/* catch OIDC callback */
if (to.path === callbackPath) {
console.info("OIDC: got signin callback request to ", to);
console.info("OIDC: got OIDC callback request to ", to);
const userMgr = getOidcUserManager();
try {
const user = await userMgr.signinRedirectCallback(location.origin + to.fullPath);
@ -47,6 +47,7 @@ router.beforeEach(async (to, from) => {
storeInit(user);
} catch (err) {
console.error("OIDC: Error handling signin callback: ", err);
showError("The login process failed: " + err);
}
/* redirect to home page */
return {name: 'home'};

View File

@ -5,7 +5,7 @@
* https://basebox.io
*/
import { reactive } from 'vue'
import {gqlQuery} from "./util/net";
import {gqlRequest} from "./util/net";
export const store = reactive({
@ -60,6 +60,18 @@ export function clearError() {
store.fatalError = "";
}
/**
* Remove a task from the store.
*
* This does not remove it from the database/server!
*/
export function removeTask(task) {
const idx = store.tasks.findIndex(item => item.id === task.id);
if (idx !== -1) {
store.tasks.splice(idx, 1);
}
}
/**
* Initialize data store and database.
*
@ -88,7 +100,7 @@ export async function storeInit(user) {
*/
try {
console.info("NOTE: If next request fails, it is probably because the user already exists. In this case, the error is ignored.");
await gqlQuery(`mutation {
await gqlRequest(`mutation {
createUser(
username: "${store.userId}",
name: "${store.userDisplayName}"
@ -108,7 +120,7 @@ export async function storeInit(user) {
}
/* Load user info, lists and todos. */
gqlQuery(`query {
gqlRequest(`query {
getUser(username: "${store.userId}") {
name
lists {
@ -131,14 +143,18 @@ export async function storeInit(user) {
/* create default list if necessary */
if (store.lists.length === 0) {
console.info("No lists found for current user; creating default list.");
gqlQuery(`mutation {
gqlRequest(`mutation {
createList(
title: "Default",
user: { username: "${store.userId}" }
) {
title
id
}
}`);
}`).then(data => {
/* Save new default list in store */
store.lists.push(data.createList);
});
}
/* fix for basebox bug: task.list is returned as an array of List objects, where we expect just a single object */

View File

@ -8,7 +8,7 @@
import {clearSession, store} from "../store";
/**
* GqlError - custom error thrown by the gqlQuery function.
* GqlError - custom error thrown by the gqlRequest function.
*
* Since the GraphQL server might through multiple errors at once,
* this error class uses a message array.
@ -102,7 +102,7 @@ class GqlError extends Error {
* Error with error message array on failure
*/
export function gqlQuery(query)
export function gqlRequest(query)
{
/* prepare fetch options */
const fetchOpt = {
@ -113,9 +113,9 @@ export function gqlQuery(query)
body: JSON.stringify({ query: query }),
};
/* if we're logged in, we add the authorization header */
if (store.session) {
fetchOpt.headers["Authorization"] = `Bearer ${store.session.token}`;
/* if we're logged in, we add the authorization header with the access token */
if (store.user) {
fetchOpt.headers["Authorization"] = `Bearer ${store.user.access_token}`;
}
console.info(`Sending request:\n${query}`);

View File

@ -1,6 +1,9 @@
/**
* Configure oidc-client-ts library and retrieve UserManager instance for authentication.
*
* The documentation of oidc-client-ts is very...thin; this might be helpful:
* <https://gist.github.com/davidamidon/24c8a6980e116e62f781be4d6239d10d>
*
*/
import { Log, UserManager, WebStorageStateStore } from 'oidc-client-ts';
@ -11,13 +14,18 @@ Log.level = (process.env.NODE_ENV === 'production') ? Log.ERROR : Log.DEBUG;
// OIDC configuration
const oidcProviderDomain = 'https://basebox-test-1.eu.auth0.com';
const clientId = '5wl8hQV1thh07rScSoJ3aN56ETuXWprg';
const clientSecret = 'QlHMvIffKLRviCcSu_bPQcf8e4dc6WeS3BwZE1r1F-9R30AFoeYEwaOazAuFenI5';
const scopes = "openid profile email name nickname";
export const callbackPath = "/auth/callback"
/* OIDC UserManager singleton */
let userMgr = null;
// OIDC Client
/**
* Get OIDC UserManager singleton.
*
* @returns OIDC UserManager singleton.
*/
export const getOidcUserManager = () => {
if (userMgr) {
return userMgr;
@ -27,11 +35,12 @@ export const getOidcUserManager = () => {
userStore: new WebStorageStateStore(),
authority: oidcProviderDomain,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: window.location.origin + callbackPath,
response_type: 'code',
response_mode: 'query',
scope: scopes,
post_logout_redirect_uri: window.location.origin + '/home?action=logout',
post_logout_redirect_uri: window.location.origin,
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: false,
filterProtocolClaims: false,