├── go
├── api
│ ├── config.json
│ ├── google_api
│ │ ├── credentials.json
│ │ └── token.json
│ ├── data
│ │ ├── role_test.go
│ │ ├── team_test.go
│ │ ├── user_test.go
│ │ ├── booking_test.go
│ │ ├── permission_test.go
│ │ ├── common.go
│ │ └── notification.go
│ ├── redis
│ │ └── redist_test.go
│ ├── endpoints
│ │ ├── permission_test.go
│ │ ├── batch-booking_test.go
│ │ ├── statistics.go
│ │ └── notification.go
│ ├── scheduler
│ │ ├── caller_command.go
│ │ ├── caller_integration_test.go
│ │ ├── data_reciever.go
│ │ └── logger.go
│ ├── ReadMe.md
│ ├── Dockerfile
│ └── security
│ │ └── validate.go
├── scheduler
│ ├── ga
│ │ ├── popgen_test.go
│ │ ├── selection.go
│ │ ├── ga_test.go
│ │ ├── domain.go
│ │ └── validate_test.go
│ ├── go.mod
│ ├── config.json
│ ├── main.go
│ ├── data
│ │ ├── utils.go
│ │ └── utils_test.go
│ ├── Dockerfile
│ └── go.sum
├── lib
│ ├── ReadMe.md
│ ├── collectionutils
│ │ ├── maputils.go
│ │ └── maputils_test.go
│ ├── dbmigrate
│ │ └── example-data
│ │ │ ├── go
│ │ │ ├── 00001_person.schema.up.sql
│ │ │ ├── 00003_owns.schema.up.sql
│ │ │ ├── 00002_dog.schema.up.sql
│ │ │ ├── 00004_owns.function.up.sql
│ │ │ └── 00005_mock.up.sql
│ │ │ └── man
│ │ │ ├── person
│ │ │ └── person.schema.sql
│ │ │ ├── owns
│ │ │ ├── owns.schema.sql
│ │ │ └── owns.function.sql
│ │ │ ├── dog
│ │ │ └── dog.schema.sql
│ │ │ └── mock
│ │ │ └── mock.sql
│ ├── restclient
│ │ └── restclient.go
│ ├── clock
│ │ └── clock.go
│ ├── logger
│ │ └── logger.go
│ ├── test_setup
│ │ ├── db_access.go
│ │ └── test_setup.go
│ ├── testutils
│ │ └── testutils.go
│ └── go.mod
└── README.md
├── .gitattributes
├── redis
├── README.md
└── Dockerfile
├── go.mod
├── google_api
├── run.py
├── login.json
├── requirements.txt
├── setup.py
├── makefile
├── google_api
│ └── go.mod
├── main.py
└── main.pyx
├── client
└── web
│ ├── example
│ ├── .npmrc
│ ├── .dockerignore
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── index.html
│ ├── src
│ │ ├── img
│ │ │ ├── tile.png
│ │ │ ├── tile2.png
│ │ │ ├── background.png
│ │ │ ├── background2.png
│ │ │ ├── background3.png
│ │ │ ├── desk_grey.svg
│ │ │ ├── desk_light.svg
│ │ │ ├── desk_white.svg
│ │ │ ├── desk_purple.svg
│ │ │ ├── room_add.svg
│ │ │ ├── room_edit.svg
│ │ │ ├── building_add.svg
│ │ │ └── building_edit.svg
│ │ ├── components
│ │ │ ├── Footer
│ │ │ │ └── index.js
│ │ │ ├── StatisticsComponent
│ │ │ │ └── statistics.module.css
│ │ │ ├── Logout
│ │ │ │ ├── LogoutButton.js
│ │ │ │ └── __test__
│ │ │ │ │ └── LogoutButton.test.js
│ │ │ ├── Navbar
│ │ │ │ ├── navbar.module.css
│ │ │ │ ├── ProfileBar.js
│ │ │ │ └── __test__
│ │ │ │ │ └── ProfileBar.test.js
│ │ │ ├── Resources
│ │ │ │ ├── resources.module.css
│ │ │ │ ├── AddBuilding.js
│ │ │ │ └── AddRoom.js
│ │ │ ├── Forms
│ │ │ │ └── FormField.js
│ │ │ ├── Profile
│ │ │ │ ├── UserListItem.js
│ │ │ │ └── __test__
│ │ │ │ │ └── UserListItem.test.js
│ │ │ ├── Role
│ │ │ │ ├── RoleLeadOption.js
│ │ │ │ └── RoleUserList.js
│ │ │ ├── User
│ │ │ │ └── user.module.css
│ │ │ └── Calendar
│ │ │ │ └── Borders.js
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── reportWebVitals.js
│ │ ├── setupTests.js
│ │ ├── index.js
│ │ ├── store
│ │ │ └── ProtectedRoute.js
│ │ ├── firebase.js
│ │ └── pages
│ │ │ ├── Home.js
│ │ │ ├── OfficeCreator.js
│ │ │ ├── Profile.js
│ │ │ ├── Calendar.js
│ │ │ ├── Statistics.js
│ │ │ ├── BookingsDesk.js
│ │ │ ├── BookingsMeetingRoom.js
│ │ │ ├── Admin.js
│ │ │ ├── Roles.js
│ │ │ └── RolesCreate.js
│ ├── .gitignore
│ ├── Dockerfile
│ └── package.json
│ └── ReadMe.md
├── db
├── sql
│ ├── user
│ │ ├── user.credential.function.authenticate.sql
│ │ ├── user.credential.function.reset_secret.sql
│ │ ├── user.credential.function.remove.sql
│ │ ├── user.credential.function.store.sql
│ │ ├── user.identifier.function.remove.sql
│ │ ├── user.credential.function.find.sql
│ │ ├── user.identifier.function.store.sql
│ │ ├── user.schema.sql
│ │ └── user.identifier.function.find.sql
│ ├── statistics
│ │ ├── statistics.schema.sql
│ │ ├── statistics.function.current_occupancy.sql
│ │ ├── statistics.function.average_utilisation.sql
│ │ ├── statistics.function.automated_ratio.sql
│ │ ├── statistics.function.utilisation.sql
│ │ └── statistics.function.yearly_utilisation.sql
│ ├── role
│ │ ├── role.user.function.store.sql
│ │ ├── role.identifier.function.remove.sql
│ │ ├── role.user.function.remove.sql
│ │ ├── role.schema.sql
│ │ ├── role.identifier.function.store.sql
│ │ ├── role.identifier.function.find.sql
│ │ └── role.user.function.find.sql
│ ├── team
│ │ ├── team.user.function.store.sql
│ │ ├── team.association.function.store.sql
│ │ ├── team.user.function.remove.sql
│ │ ├── team.identifier.function.remove.sql
│ │ ├── team.association.function.remove.sql
│ │ ├── team.identifier.function.store.sql
│ │ ├── team.schema.sql
│ │ ├── team.association.function.find.sql
│ │ ├── team.user.function.find.sql
│ │ └── team.identifier.function.find.sql
│ ├── resource
│ │ ├── resource.room.association.function.store.sql
│ │ ├── resource.building.function.remove.sql
│ │ ├── resource.room.function.remove.sql
│ │ ├── resource.identifier.function.remove.sql
│ │ ├── resource.building.function.store.sql
│ │ ├── resource.room.association.function.remove.sql
│ │ ├── resource.room.function.store.sql
│ │ ├── resource.identifier.function.store.sql
│ │ ├── resource.schema.sql
│ │ ├── resource.room.association.function.find.sql
│ │ ├── resource.building.function.find.sql
│ │ ├── resource.room.function.find.sql
│ │ └── resource.identifier.function.find.sql
│ ├── permission
│ │ ├── permission.function.store.sql
│ │ ├── permission.function.remove.sql
│ │ └── permission.schema.sql
│ ├── booking
│ │ ├── booking.meeting_room.function.remove.sql
│ │ ├── booking.identifier.function.remove.sql
│ │ ├── booking.meeting_room.function.store.sql
│ │ ├── booking.identifier.function.store.sql
│ │ └── booking.meeting_room.function.find.sql
│ └── initdb.sh
├── .gitignore
└── ReadMe.md
├── run.sh
├── .gitignore
├── proxy
├── Dockerfile
├── Arche-Server.cert
└── Arche-Server.key
├── .github
└── workflows
│ ├── deploy.yml
│ ├── goScheduler.yml
│ ├── react.yml
│ ├── go.yml
│ ├── coverage_go.yml
│ └── metatestPullRequest.yml
├── python
├── login.py
├── mock-data-config.json
├── config.py
├── scheduler-call.py
├── break.py
└── mockdatapool.json
├── setup.py
├── documentation
└── api
│ └── Smart Hybrid Workforce System.postman_environment.json
└── .env
/go/api/config.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/go/api/google_api/credentials.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/redis/README.md:
--------------------------------------------------------------------------------
1 | # Redis read me file
--------------------------------------------------------------------------------
/go/api/data/role_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
--------------------------------------------------------------------------------
/go/api/data/team_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
--------------------------------------------------------------------------------
/go/api/data/user_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
--------------------------------------------------------------------------------
/go/api/redis/redist_test.go:
--------------------------------------------------------------------------------
1 | package redis
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module scheduler
2 |
3 | go 1.18
4 |
--------------------------------------------------------------------------------
/go/api/data/booking_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
--------------------------------------------------------------------------------
/go/api/data/permission_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
--------------------------------------------------------------------------------
/go/scheduler/ga/popgen_test.go:
--------------------------------------------------------------------------------
1 | package ga
2 |
--------------------------------------------------------------------------------
/google_api/run.py:
--------------------------------------------------------------------------------
1 | import main
2 | main.main()
--------------------------------------------------------------------------------
/client/web/example/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
2 |
--------------------------------------------------------------------------------
/db/sql/user/user.credential.function.authenticate.sql:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db/sql/user/user.credential.function.reset_secret.sql:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | docker-compose --env-file ./.env --build up
--------------------------------------------------------------------------------
/go/api/endpoints/permission_test.go:
--------------------------------------------------------------------------------
1 | package endpoints
2 |
--------------------------------------------------------------------------------
/go/api/endpoints/batch-booking_test.go:
--------------------------------------------------------------------------------
1 | package endpoints
2 |
--------------------------------------------------------------------------------
/db/.gitignore:
--------------------------------------------------------------------------------
1 | # Postgres Data
2 | postgres-data
3 | postgres-data-test
--------------------------------------------------------------------------------
/google_api/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "username":"",
3 | "password":""
4 | }
--------------------------------------------------------------------------------
/google_api/requirements.txt:
--------------------------------------------------------------------------------
1 | google-api-python-client google-auth-httplib2 google-auth-oauthlib
--------------------------------------------------------------------------------
/redis/Dockerfile:
--------------------------------------------------------------------------------
1 | # build image
2 | FROM redis:6.2.7
3 |
4 | WORKDIR /data
5 | RUN set bind 0.0.0.0
--------------------------------------------------------------------------------
/client/web/example/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | .dockerignore
4 | Dockerfile
5 | Dockerfile.prod
--------------------------------------------------------------------------------
/client/web/example/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS statistics;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
--------------------------------------------------------------------------------
/go/lib/ReadMe.md:
--------------------------------------------------------------------------------
1 | # General
2 |
3 | All functions that are shared accross the go directory will be stored in this `lib` directory
4 |
--------------------------------------------------------------------------------
/client/web/example/src/img/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/src/img/tile.png
--------------------------------------------------------------------------------
/client/web/example/src/img/tile2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/src/img/tile2.png
--------------------------------------------------------------------------------
/client/web/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/public/favicon.ico
--------------------------------------------------------------------------------
/client/web/example/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/public/logo192.png
--------------------------------------------------------------------------------
/client/web/example/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/public/logo512.png
--------------------------------------------------------------------------------
/client/web/example/src/img/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/src/img/background.png
--------------------------------------------------------------------------------
/client/web/example/src/img/background2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/src/img/background2.png
--------------------------------------------------------------------------------
/client/web/example/src/img/background3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/COS301-SE-2022/Smart-Hybrid-Workforce-manager/HEAD/client/web/example/src/img/background3.png
--------------------------------------------------------------------------------
/google_api/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from Cython.Build import cythonize
3 | import sys
4 |
5 | setup(
6 | ext_modules = cythonize("main.pyx")
7 | )
--------------------------------------------------------------------------------
/google_api/makefile:
--------------------------------------------------------------------------------
1 | make:
2 | python3 setup.py build_ext --inplace
3 | clean:
4 | rm -f -r build
5 | rm -f *.c *.so
6 | rm -f -r __pycache__
7 | run:
8 | python3 run.py
9 |
--------------------------------------------------------------------------------
/go/lib/collectionutils/maputils.go:
--------------------------------------------------------------------------------
1 | package collectionutils
2 |
3 | func MapHasKey[K comparable, V any](_map map[K]V, key K) bool {
4 | _, ok := _map[key]
5 | return ok
6 | }
7 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/go/00001_person.schema.up.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS "pets";
2 |
3 | CREATE TABLE pets.person (
4 | id SERIAL PRIMARY KEY,
5 | name VARCHAR(255),
6 | age INT
7 | );
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/man/person/person.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS "pets";
2 |
3 | CREATE TABLE pets.person (
4 | id SERIAL PRIMARY KEY,
5 | name VARCHAR(255),
6 | age INT
7 | );
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/go/00003_owns.schema.up.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE pets.owns (
2 | pid INTEGER REFERENCES pets.person(id),
3 | did INTEGER REFERENCES pets.dog(id),
4 | PRIMARY KEY (pid, did)
5 | );
6 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/man/owns/owns.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE pets.owns (
2 | pid INTEGER REFERENCES pets.person(id),
3 | did INTEGER REFERENCES pets.dog(id),
4 | PRIMARY KEY (pid, did)
5 | );
6 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/man/dog/dog.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS "pets";
2 |
3 | CREATE TABLE "pets".dog (
4 | id SERIAL PRIMARY KEY,
5 | "name" VARCHAR(255),
6 | breed VARCHAR(255)
7 | );
8 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/go/00002_dog.schema.up.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS "pets";
2 |
3 | CREATE TABLE "pets".dog (
4 | id SERIAL PRIMARY KEY,
5 | "name" VARCHAR(255),
6 | breed VARCHAR(255)
7 | );
8 |
--------------------------------------------------------------------------------
/go/api/scheduler/caller_command.go:
--------------------------------------------------------------------------------
1 | package scheduler
2 |
3 | // Command used by the call function
4 | type CallCommand interface {
5 | Invoke() error
6 | }
7 |
8 | type WeeklySchedulerCommand struct {
9 | Data *SchedulerData
10 | }
11 |
--------------------------------------------------------------------------------
/go/lib/restclient/restclient.go:
--------------------------------------------------------------------------------
1 | package restclient
2 |
3 | import "net/http"
4 |
5 | // An HTTP client interface, used to allow easier mocking for testing
6 |
7 | type HTTPClient interface {
8 | Do(req *http.Request) (*http.Response, error)
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bat
2 | .vscode
3 | redis/dump.rdb
4 | smart_scheduler/.idea
5 | *.pyc
6 | *.rdb
7 | .env
8 | go/api/google_api/*.json
9 | go/api/google_api/credentials.json
10 | go/api/google_api/token.json
11 | google_api/credentials.json
12 | google_api/token.json
13 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Footer = () =>
4 | {
5 | return (
6 |
UNIVERSITY OF PRETORIA
EPI-USE LABS
2022
7 | )
8 | }
9 |
10 | export default Footer
--------------------------------------------------------------------------------
/db/sql/role/role.user.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.user_store(
2 | _role_id uuid,
3 | _user_id uuid
4 | )
5 | RETURNS BOOLEAN AS
6 | $$
7 | BEGIN
8 | INSERT INTO role.user(role_id, user_id)
9 | VALUES (_role_id, _user_id);
10 | RETURN true;
11 | END
12 | $$ LANGUAGE plpgsql;
13 |
--------------------------------------------------------------------------------
/proxy/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:latest
2 |
3 | WORKDIR /etc/nginx
4 | RUN mkdir ./ssl
5 | COPY ./proxy/nginx.conf ./conf.d/default.conf
6 | COPY ./proxy/Arche-Server.cert ./ssl/Arche-Server.cert
7 | COPY ./proxy/Arche-Server.key ./ssl/Arche-Server.key
8 | EXPOSE 8080
9 | ENTRYPOINT [ "nginx" ]
10 | CMD [ "-g", "daemon off;" ]
--------------------------------------------------------------------------------
/db/sql/team/team.user.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.user_store(
2 | _team_id uuid, -- NOT NULLABLE
3 | _user_id uuid
4 | )
5 | RETURNS BOOLEAN AS
6 | $$
7 | BEGIN
8 | INSERT INTO team.user(team_id, user_id)
9 | VALUES (_team_id, _user_id);
10 | RETURN true;
11 | END
12 | $$ LANGUAGE plpgsql;
13 |
--------------------------------------------------------------------------------
/go/lib/clock/clock.go:
--------------------------------------------------------------------------------
1 | package clock
2 |
3 | import "time"
4 |
5 | // The clock interface is used to make code more testable by allowing the Now function to be mocked
6 |
7 | type Clock interface {
8 | Now() time.Time
9 | }
10 |
11 | type RealClock struct {
12 | }
13 |
14 | func (c *RealClock) Now() time.Time {
15 | return time.Now()
16 | }
17 |
--------------------------------------------------------------------------------
/go/api/data/common.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "database/sql"
4 |
5 | //////////////////////////////////////////////////
6 | // Mappers
7 |
8 | // mapString
9 | func mapString(rows *sql.Rows) (interface{}, error) {
10 | var s string
11 | err := rows.Scan(
12 | &s,
13 | )
14 | if err != nil {
15 | return nil, err
16 | }
17 | return s, nil
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Build the Docker image
17 | run: docker compose up --build -d
18 |
--------------------------------------------------------------------------------
/client/web/example/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | // const title = screen.getByText(/SMART-HYBRID WORKFORCE MANAGER/i);
7 | const title = screen.getByText(/WELCOME BACK/i);
8 | expect(title).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/go/00004_owns.function.up.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION pets.get_ownership()
2 | RETURNS TABLE(pname VARCHAR, dname VARCHAR)
3 | AS
4 | $$
5 | BEGIN
6 | RETURN QUERY
7 | SELECT p.name, d.name
8 | FROM pets.owns AS o
9 | INNER JOIN pets.person AS p ON o.pid = p.id
10 | INNER JOIN pets.dog AS d ON o.did = d.id;
11 | END
12 | $$
13 | LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/man/owns/owns.function.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION pets.get_ownership()
2 | RETURNS TABLE(pname VARCHAR, dname VARCHAR)
3 | AS
4 | $$
5 | BEGIN
6 | RETURN QUERY
7 | SELECT p.name, d.name
8 | FROM pets.owns AS o
9 | INNER JOIN pets.person AS p ON o.pid = p.id
10 | INNER JOIN pets.dog AS d ON o.did = d.id;
11 | END
12 | $$
13 | LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/scheduler/go.mod:
--------------------------------------------------------------------------------
1 | module scheduler
2 |
3 | go 1.18
4 |
5 | replace lib => ../lib
6 |
7 | require (
8 | github.com/gorilla/mux v1.8.0
9 | github.com/stretchr/testify v1.8.0
10 | lib v0.0.0-00010101000000-000000000000
11 | )
12 |
13 | require (
14 | github.com/davecgh/go-spew v1.1.1 // indirect
15 | github.com/pmezard/go-difflib v1.0.0 // indirect
16 | gopkg.in/yaml.v3 v3.0.1 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/client/web/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/go/api/data/notification.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | //////////////////////////////////////////////////
4 | // Structures and Variables
5 |
6 | // Notification structure
7 | type Notification struct {
8 | To *string `json:"to,omitempty"`
9 | StartDate *string `json:"sDate,omitempty"`
10 | StartTime *string `json:"sTime,omitempty"`
11 | EndDate *string `json:"eDate,omitempty"`
12 | EndTime *string `json:"eTime,omitempty"`
13 | }
14 |
--------------------------------------------------------------------------------
/client/web/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/man/mock/mock.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO pets.person (name, age)
2 | VALUES
3 | ('Chris Hadfield', 62),
4 | ('John Smith', 22);
5 |
6 | INSERT INTO pets.dog ("name", breed)
7 | VALUES
8 | ('Harry', 'German Sheppard'),
9 | ('Lucky', 'Labrador Retriever'),
10 | ('Jenny', 'Mix'),
11 | ('Fixie', 'Jack Russel');
12 |
13 | INSERT INTO pets.owns (pid, did)
14 | VALUES
15 | (1, 1),
16 | (2, 2),
17 | (2, 3);
--------------------------------------------------------------------------------
/client/web/example/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/go/lib/dbmigrate/example-data/go/00005_mock.up.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO pets.person (name, age)
2 | VALUES
3 | ('Chris Hadfield', 62),
4 | ('John Smith', 22);
5 |
6 | INSERT INTO pets.dog ("name", breed)
7 | VALUES
8 | ('Harry', 'German Sheppard'),
9 | ('Lucky', 'Labrador Retriever'),
10 | ('Jenny', 'Mix'),
11 | ('Fixie', 'Jack Russel');
12 |
13 | INSERT INTO pets.owns (pid, did)
14 | VALUES
15 | (1, 1),
16 | (2, 2),
17 | (2, 3);
--------------------------------------------------------------------------------
/client/web/example/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 |
6 | // run tests in client/web/example/src using 'npm test'
7 | // to get test coverage, use command 'npm test -- --coverage --watchAll=false' (note the additional -- flag)
8 |
9 | import '@testing-library/jest-dom';
10 |
--------------------------------------------------------------------------------
/python/login.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | def login_get_token(email: str = None, password: str = None) -> str:
4 | body: dict[str] = {
5 | "id": None,
6 | "secret": None,
7 | "active": None,
8 | "FailedAttempts": None,
9 | "LastAccessed": None,
10 | "Identifier": None
11 | }
12 | resp = requests.post("http://localhost:8100/api/user/login", json=body)
13 | return "bearer " + str(resp.json()["token"])
14 |
15 | print(login_get_token())
--------------------------------------------------------------------------------
/client/web/example/src/components/StatisticsComponent/statistics.module.css:
--------------------------------------------------------------------------------
1 | /* Statistics styling */
2 | .statisticsContainer
3 | {
4 | background-color: rgba(255, 255, 255, 0);
5 | overflow-y: scroll;
6 | }
7 |
8 | .statisticsHeadingContainer
9 | {
10 | height: 10vh;
11 |
12 | background-color: white;
13 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
14 | color: #374146;
15 | }
16 |
17 | .statisticsHeading
18 | {
19 | font-size: 3vw;
20 | letter-spacing: 0.3vw;
21 |
22 | margin-left: 3vw;
23 | }
24 |
--------------------------------------------------------------------------------
/go/scheduler/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "daily_config": {
3 | "seed" : 112,
4 | "populationSize": 300,
5 | "generations": 500,
6 | "mutationRate": 0.40,
7 | "crossOverRate": 0.55,
8 | "tournamentSize": 20,
9 | "time_limit_seconds": 3
10 | },
11 | "weekly_config": {
12 | "seed" : 112,
13 | "populationSize": 400,
14 | "generations": 100,
15 | "mutationRate": 1.0,
16 | "crossOverRate": 0.0,
17 | "tournamentSize": 20,
18 | "time_limit_seconds": 2
19 | }
20 | }
--------------------------------------------------------------------------------
/.github/workflows/goScheduler.yml:
--------------------------------------------------------------------------------
1 | name: 'Go Scheduler Test'
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 |
12 | - name: Set up Go 1.18
13 | uses: actions/setup-go@v3
14 | with:
15 | go-version: 1.18
16 |
17 | - name: Build
18 | working-directory: ./go/scheduler
19 | run: go build -v ./...
20 |
21 | - name: Test
22 | working-directory: ./go/scheduler
23 | run: go test -cover -v ./...
24 |
--------------------------------------------------------------------------------
/client/web/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 | );
11 |
12 | // If you want to start measuring performance in your app, pass a function
13 | // to log results (for example: reportWebVitals(console.log))
14 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
15 | reportWebVitals();
16 |
--------------------------------------------------------------------------------
/client/web/example/Dockerfile:
--------------------------------------------------------------------------------
1 | # COPIED FROM https://mherman.org/blog/dockerizing-a-react-app/
2 |
3 | # pull official base image
4 | FROM node:13.12.0-alpine
5 |
6 | # set working directory
7 | WORKDIR /app
8 |
9 | RUN npm install -g nodemon
10 |
11 | # add `/app/node_modules/.bin` to $PATH
12 | ENV PATH /app/node_modules/.bin:$PATH
13 |
14 | # install app dependencies
15 | COPY package.json ./
16 | COPY package-lock.json ./
17 | RUN npm install --silent
18 | RUN npm install react-scripts@3.4.1 -g --silent
19 |
20 | # add app
21 | COPY . ./
22 |
23 | # start app
24 | CMD ["npm", "start"]
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.function.current_occupancy.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION statistics.current_occupancy ()
2 | RETURNS TABLE (
3 | current_occupied bigint,
4 | total bigint
5 | ) AS
6 | $$
7 | BEGIN
8 | RETURN QUERY
9 | SELECT (SELECT COUNT(DISTINCT(b.resource_id))
10 | FROM booking.identifier b
11 | WHERE DATE_TRUNC('day', b.start::date) = DATE_TRUNC('day', current_date) AND b.booked = TRUE
12 | ) AS current_occupied,
13 | (SELECT COUNT(DISTINCT(b.id))
14 | FROM resource.identifier b
15 | ) AS total;
16 | RETURN;
17 | END
18 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/api/scheduler/caller_integration_test.go:
--------------------------------------------------------------------------------
1 | //go:build integration
2 | // +build integration
3 |
4 | package scheduler
5 |
6 | import (
7 | "api/db"
8 | "testing"
9 | )
10 |
11 | func Test_Integration_call(t *testing.T) {
12 | // t.Setenv("DATABASE_DSN", "host=127.0.0.1 port=5432 user=admin dbname=arche sslmode=disable")
13 | // t.Setenv("DATABASE_MAX_IDLE_CONNECTIONS", "5")
14 | // t.Setenv("DATABASE_MAX_OPEN_CONNECTIONS", "25")
15 | // t.Setenv("DATABASE_URL", "postgresql://admin:admin@localhost:5432/db?schema=public")
16 | // err := db.RegisterAccess()
17 | // if err != nil {
18 | // t.Fatal(err)
19 | // }
20 | }
21 |
--------------------------------------------------------------------------------
/client/web/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/db/sql/team/team.association.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.association_store(
2 | _team_id uuid, -- NOT NULLABLE
3 | _team_id_association uuid
4 | )
5 | RETURNS BOOLEAN AS
6 | $$
7 | BEGIN
8 | IF (_team_id IS NOT NULL AND EXISTS(SELECT 1 FROM team.association WHERE team_id = _team_id)) THEN
9 | UPDATE team.association
10 | SET team_id_association = _team_id_association
11 | WHERE team_id = _team_id;
12 | ELSE
13 | INSERT INTO team.association(team_id, team_id_association)
14 | VALUES (_team_id, _team_id_association);
15 | END IF;
16 | RETURN true;
17 | END
18 | $$ LANGUAGE plpgsql;
19 |
--------------------------------------------------------------------------------
/go/lib/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "log"
5 | "os"
6 | "path"
7 | "strings"
8 | )
9 |
10 | var (
11 | _, filename = path.Split(os.Args[0])
12 | prefix = strings.ToUpper(filename)
13 | HTTP = log.New(os.Stdout, prefix+" [HTTP] ", log.LstdFlags)
14 | Access = log.New(os.Stdout, prefix+" [ACCESS] ", log.LstdFlags)
15 | Warn = log.New(os.Stdout, prefix+" [WARN] ", log.LstdFlags)
16 | Info = log.New(os.Stdout, prefix+" [INFO] ", log.LstdFlags)
17 | Debug = log.New(os.Stdout, prefix+" [DEBUG] ", log.LstdFlags)
18 | Error = log.New(os.Stdout, prefix+" [ERROR] ", log.LstdFlags)
19 | )
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from os.path import exists
2 | from typing import Set
3 |
4 | class Setup:
5 | def __init__(self):
6 | self.env_file = None
7 | def run(self):
8 | self.setEnviorment()
9 |
10 | def setEnviorment(self):
11 | #POSTGRES
12 | user = input("Please enter user for postgress db (defualt: admin)")
13 | password = input("Please enter password for postgress db (defualt: admin)")
14 | db = input("Please enter db for postgress db (defualt: arche)")
15 | #API Notifications
16 | #Redis Database
17 |
18 | if __name__ == '__main__':
19 | setup = Setup()
20 | setup.run()
--------------------------------------------------------------------------------
/client/web/example/src/store/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | //Source code provided by Lester Fernandez
2 | //https://github.com/lesterfernandez
3 | import { Navigate, Outlet, useLocation } from "react-router-dom";
4 | import { useContext } from "react";
5 | import { UserContext } from "../App";
6 |
7 |
8 |
9 | const ProtectedRoute = () => {
10 | const { userData,setUserData } = useContext(UserContext);
11 | const location = useLocation();
12 | if(userData && userData.expr_time > Date.now()){
13 | return ;
14 | }
15 | else
16 | return
17 | }
18 |
19 | export default ProtectedRoute;
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.association.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_association_store(
2 | _room_id uuid,
3 | _room_id_association uuid
4 | )
5 | RETURNS BOOLEAN AS
6 | $$
7 | BEGIN
8 | IF (_room_id IS NOT NULL AND EXISTS(SELECT 1 FROM resource.room_association WHERE room_id = _room_id)) THEN
9 | UPDATE resource.room_association
10 | SET room_id_association = _room_id_association
11 | WHERE room_id = _room_id;
12 | ELSE
13 | INSERT INTO resource.room_association(room_id, room_id_association)
14 | VALUES (_room_id, _room_id_association);
15 | END IF;
16 | RETURN true;
17 | END
18 | $$ LANGUAGE plpgsql;
19 |
--------------------------------------------------------------------------------
/documentation/api/Smart Hybrid Workforce System.postman_environment.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "c1751c2d-4845-42ca-a846-6219ea88e56b",
3 | "name": "Smart Hybrid Workforce System",
4 | "values": [
5 | {
6 | "key": "REST_BASE_URL",
7 | "value": "http://localhost:8100/api",
8 | "type": "default",
9 | "enabled": true
10 | },
11 | {
12 | "key": "TOKEN",
13 | "value": "",
14 | "type": "default",
15 | "enabled": true
16 | },
17 | {
18 | "key": "USER",
19 | "value": "",
20 | "type": "default",
21 | "enabled": true
22 | }
23 | ],
24 | "_postman_variable_scope": "environment",
25 | "_postman_exported_at": "2022-09-28T09:53:17.401Z",
26 | "_postman_exported_using": "Postman/9.31.12"
27 | }
--------------------------------------------------------------------------------
/db/sql/resource/resource.building.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.building_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | name VARCHAR(256),
7 | location VARCHAR(256),
8 | dimension VARCHAR(256)
9 | ) AS
10 | $$
11 | BEGIN
12 |
13 | IF NOT EXISTS (
14 | SELECT 1
15 | FROM resource.building as b
16 | WHERE b.id = _id
17 | FOR UPDATE
18 | ) THEN
19 | RAISE EXCEPTION 'invalid_building'
20 | USING HINT = 'Please check the provided building id parameter';
21 | END IF;
22 |
23 | RETURN QUERY
24 | DELETE FROM resource.building as a WHERE a.id = _id
25 | RETURNING *;
26 |
27 | END
28 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/role/role.identifier.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | name VARCHAR(256),
7 | color VARCHAR(256),
8 | lead_id uuid,
9 | date_added TIMESTAMP
10 | ) AS
11 | $$
12 | BEGIN
13 |
14 | IF NOT EXISTS (
15 | SELECT 1
16 | FROM role.identifier as b
17 | WHERE b.id = _id
18 | FOR UPDATE
19 | ) THEN
20 | RAISE EXCEPTION 'invalid_role'
21 | USING HINT = 'Please check the provided role id parameter';
22 | END IF;
23 |
24 | RETURN QUERY
25 | DELETE FROM role.identifier as a WHERE a.id = _id
26 | RETURNING *;
27 |
28 | END
29 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/api/google_api/token.json:
--------------------------------------------------------------------------------
1 | {
2 | "token": "ya29.a0Aa4xrXONpxIuGUfpTPVPbaPICRoPVx_vP8kvL1KknUYIGPiuOo5qkHNDxNNuy-ZANH_GLGU9p-xCEuA51Gy-IGFWoBk3vtgIN98h5N1yCTPQ3VU0lBlsKJ-2p3b_5o0nZqZlIfmXJZKddLByBCkYSqAt6R_kaCgYKATASARESFQEjDvL9lSOxI3MY_V90wEN4EDloPw0163",
3 | "refresh_token": "1//03B2NIewkyVy8CgYIARAAGAMSNwF-L9Irf3tjfJtu82a8Yatm2P6z5VXgv25Ss9MDJXvEvwU8aKVk-GfruoHG2wf9sRz-Hik3Nio",
4 | "token_uri": "https://oauth2.googleapis.com/token",
5 | "client_id": "927724428775-u8274a740jsabuvamdj23d5rf899t760.apps.googleusercontent.com",
6 | "client_secret": "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",
7 | "scopes": [
8 | "https://www.googleapis.com/auth/calendar"
9 | ],
10 | "expiry": "2022-09-26T11:10:43.280707Z"
11 | }
--------------------------------------------------------------------------------
/go/README.md:
--------------------------------------------------------------------------------
1 | # Testing in Go
2 | ## File Naming
3 | Files that are used for testing in Go should be named in the following way:
4 | > the name of the file or module being tested, followed by a _test.go suffix, e.g. A test for a file, funcs.go, would be named, funcs_test.go.
5 |
6 |
7 | ## File structure
8 | The test files should be kept in the same directory as the code that they are testing, hence the following example structure must be followed
9 | ```bash
10 | .
11 | ├── dir1
12 | │ ├── file1.go
13 | │ └── file1_test.go
14 | ├── dir2
15 | │ ├── file2.go
16 | │ ├── file2_test.go
17 | │ ├── file3.go
18 | │ └── file3_test.go
19 | ```
20 | Further, the testing file should be declared as being in the same package as it is testing.
21 |
--------------------------------------------------------------------------------
/db/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Database configuration
2 |
3 | Docker will automatically run **sql/initdb.sh** which will create the schema and stored procedures.
4 |
5 | ## General Layout
6 |
7 | Each schema and its' relating stored procedures should be stored in its own directory.
8 |
9 | ## Creating a schema file
10 |
11 | All schema files should be labelled: `*.schema.*sql*` where \* is any character string.
12 |
13 | The naming convention for schema's is as follows: `RELATIONNAME.schema.sql`
14 |
15 | ## Creating a stored procedure or function
16 |
17 | All functions and procedures will be stored in their own respective file under the same directory as the schema file
18 |
19 | The naming convention for functions is as follows: `RELATIONNAME.function.FUNCTIONNAME.sql`
20 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | building_id uuid,
7 | name VARCHAR(256),
8 | xcoord float,
9 | ycoord float,
10 | zcoord float,
11 | dimension VARCHAR(256)
12 | ) AS
13 | $$
14 | BEGIN
15 |
16 | IF NOT EXISTS (
17 | SELECT 1
18 | FROM resource.room as b
19 | WHERE b.id = _id
20 | FOR UPDATE
21 | ) THEN
22 | RAISE EXCEPTION 'invalid_resource'
23 | USING HINT = 'Please check the provided resource id parameter';
24 | END IF;
25 |
26 | RETURN QUERY
27 | DELETE FROM resource.room as a WHERE a.id = _id
28 | RETURNING *;
29 |
30 | END
31 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.function.average_utilisation.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION statistics.average_utilisation
2 | (
3 | _start_date DATE DEFAULT current_date - interval '7 days',
4 | _end_date DATE DEFAULT current_date
5 | )
6 | RETURNS float AS
7 | $$
8 | DECLARE
9 | num_days INTEGER;
10 | BEGIN
11 | RETURN
12 | (SELECT AVG(COALESCE(((SELECT COUNT(DISTINCT b.start::date)
13 | FROM booking.identifier b
14 | WHERE b.resource_id = r.id AND (b.start::date BETWEEN _start_date AND _end_date) AND b.booked = TRUE
15 | GROUP BY b.resource_id
16 | )/(_end_date::date - _start_date::date + 1)::float)*100, 0)::float)
17 | FROM resource.identifier r);
18 | END
19 | $$ LANGUAGE plpgsql;
20 |
--------------------------------------------------------------------------------
/db/sql/role/role.user.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.user_remove(
2 | _role_id uuid,
3 | _user_id uuid
4 | )
5 | RETURNS TABLE (
6 | role_id uuid,
7 | user_id uuid,
8 | date_added TIMESTAMP
9 | ) AS
10 | $$
11 | BEGIN
12 |
13 | IF NOT EXISTS (
14 | SELECT 1
15 | FROM role.user as b
16 | WHERE b.role_id = _role_id
17 | AND b.user_id = _user_id
18 | FOR UPDATE
19 | ) THEN
20 | RAISE EXCEPTION 'invalid_user_role'
21 | USING HINT = 'Please check the provided role and user parameter';
22 | END IF;
23 |
24 | RETURN QUERY
25 | DELETE FROM role.user as a
26 | WHERE a.role_id = _role_id
27 | AND a.user_id = _user_id
28 | RETURNING *;
29 |
30 | END
31 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/team/team.user.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.user_remove(
2 | _team_id uuid,
3 | _user_id uuid
4 | )
5 | RETURNS TABLE (
6 | team_id uuid,
7 | user_id uuid,
8 | date_added TIMESTAMP
9 | ) AS
10 | $$
11 | BEGIN
12 |
13 | IF NOT EXISTS (
14 | SELECT 1
15 | FROM team.user as b
16 | WHERE b.team_id = _team_id
17 | AND b.user_id = _user_id
18 | FOR UPDATE
19 | ) THEN
20 | RAISE EXCEPTION 'invalid_team_user'
21 | USING HINT = 'Please check the provided team and user parameter';
22 | END IF;
23 |
24 | RETURN QUERY
25 | DELETE FROM team.user as a
26 | WHERE a.team_id = _team_id
27 | AND a.user_id = _user_id
28 | RETURNING *;
29 |
30 | END
31 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/team/team.identifier.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | name VARCHAR(256),
7 | color VARCHAR(256),
8 | capacity INT,
9 | picture TEXT,
10 | priority INT,
11 | team_lead_id uuid,
12 | date_created TIMESTAMP
13 | ) AS
14 | $$
15 | BEGIN
16 |
17 | IF NOT EXISTS (
18 | SELECT 1
19 | FROM team.identifier as b
20 | WHERE b.id = _id
21 | FOR UPDATE
22 | ) THEN
23 | RAISE EXCEPTION 'invalid_team'
24 | USING HINT = 'Please check the provided team id parameter';
25 | END IF;
26 |
27 | RETURN QUERY
28 | DELETE FROM team.identifier as a WHERE a.id = _id
29 | RETURNING *;
30 |
31 | END
32 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/api/ReadMe.md:
--------------------------------------------------------------------------------
1 | # General
2 |
3 | The api will be compiled and run through Docker as specified in `DockerFile`
4 |
5 | This image and its corresponding container need to be restarted in order to compile changes made to the api
6 |
7 | # Structure
8 |
9 | ## Endpoints
10 |
11 | All api endpoints are stored in `endpoints`. The endpoints are routed from main.go
12 |
13 | ## Database conenction
14 |
15 | The functions managing database conenctions are stored in `db`
16 |
17 | ## Data access
18 |
19 | All functions relating to data access from the postgres database is stored in `data`
20 |
21 | ## Testing
22 |
23 | `go test` this will just run the tests normally
24 | `go test -v` this will run the tests in verbose mode
25 | `go test -cover` this will run the test and include code coverage
26 |
--------------------------------------------------------------------------------
/db/sql/user/user.credential.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".credential_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id VARCHAR(256),
6 | secret VARCHAR(256),
7 | identifier VARCHAR(256),
8 | "type" "user".credential_type,
9 | active BOOLEAN,
10 | failed_attempts INT,
11 | last_accessed TIMESTAMP
12 | ) AS
13 | $$
14 | BEGIN
15 |
16 | IF NOT EXISTS (
17 | SELECT 1
18 | FROM "user".credential as b
19 | WHERE b.id = _id
20 | FOR UPDATE
21 | ) THEN
22 | RAISE EXCEPTION 'invalid_user'
23 | USING HINT = 'Please check the provided user id parameter';
24 | END IF;
25 |
26 | RETURN QUERY
27 | DELETE FROM "user".credential as a WHERE a.id = _id
28 | RETURNING *;
29 |
30 | END
31 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/python/mock-data-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "building_id": "3cfb9800-c8ec-4dcc-b746-39518457ca49",
3 | "create_user": {
4 | "num_users": 12,
5 | "password_override": "P@ssword123",
6 | "email_domains": ["@geemail.com"],
7 | "lowest_preferred_start_time": "2022-08-24T09:00:00.000Z",
8 | "highest_preferred_end_time": "2022-08-24T17:00:00.000Z",
9 | "preferred_time_step_minutes": 180,
10 | "work_from_home_probability": 0.8,
11 | "teams_override": null,
12 | "team_probabilities": [0.00, 0.3, 0.4, 0.2],
13 | "role_probabilities": [0.1, 0.2, 0.3, 0.3, 0.1],
14 | "office_days_options": [1, 2, 3, 4, 5],
15 | "office_days_probabilities": [0.0, 0.00, 0.0, 0.0, 1.0],
16 | "no_preferred_desk_probability": 0.15
17 | }
18 | }
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Database
2 | DATABASE_DSN="host=172.17.0.1 port=5432 user=admin dbname=arche sslmode=disable"
3 | DATABASE_MAX_IDLE_CONNECTIONS=5
4 | DATABASE_MAX_OPEN_CONNECTIONS=25
5 | DATABASE_URL="postgresql://admin:admin@localhost:5432/db?schema=public"
6 | # Postgres
7 | POSTGRES_USER="admin"
8 | POSTGRES_PASSWORD="admin"
9 | POSTGRES_DB="arche"
10 |
11 | #Client
12 | REACT_APP_TEAM_PICTURE = 'https://firebasestorage.googleapis.com/v0/b/arche-6bd39.appspot.com/o/teams%2FTeamDefault.png?alt=media&token=66cbabd9-a01f-47b9-9861-89b7aa523697'
13 |
14 | # API Notifications
15 | SENDER="archedevelop@gmail.com"
16 | PASSWORD="bgnqbvtdksyigqhk"
17 |
18 | # Scheduler endpoint
19 | SCHEDULER_ADDR="http://arche-scheduler:8080/api/scheduler"
20 |
21 | # Redis Database
22 | REDIS_ADDR="redis:6379"
23 | REDIS_PASSWORD="archepassword1234"
--------------------------------------------------------------------------------
/db/sql/role/role.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS role;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3 |
4 | CREATE TABLE IF NOT EXISTS role.identifier (
5 | id uuid DEFAULT uuid_generate_v4(),
6 | name VARCHAR(256) UNIQUE NOT NULL CHECK(name <> ''),
7 | color VARCHAR(256),
8 | lead_id uuid REFERENCES "user".identifier(id) ON DELETE SET NULL,
9 | date_added TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
10 |
11 | PRIMARY KEY (id)
12 | );
13 |
14 | CREATE TABLE IF NOT EXISTS role.user (
15 | role_id uuid NOT NULL REFERENCES role.identifier(id) ON DELETE CASCADE,
16 | user_id uuid NOT NULL REFERENCES "user".identifier(id) ON DELETE CASCADE,
17 | date_added TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
18 |
19 | PRIMARY KEY (role_id, user_id)
20 | );
--------------------------------------------------------------------------------
/go/scheduler/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "lib/logger"
5 | "net/http"
6 | "os"
7 | "scheduler/endpoints"
8 | "time"
9 |
10 | "github.com/gorilla/mux"
11 | )
12 |
13 | func main() {
14 | // Route endpoints
15 | router := mux.NewRouter().StrictSlash(true)
16 |
17 | // Scheduler endpoints
18 | schedulerRouter := router.PathPrefix("/api/scheduler").Subrouter()
19 | err := endpoints.SchedulerHandlers(schedulerRouter)
20 | if err != nil {
21 | logger.Error.Fatal(err)
22 | os.Exit(-1)
23 | }
24 |
25 | // Start API on port 8080 in its docker container
26 | logger.Info.Println("Starting API on 8080")
27 | server := http.Server{
28 | Addr: ":8080",
29 | Handler: router,
30 | ReadHeaderTimeout: 5 * time.Second,
31 | }
32 | logger.Error.Fatal(server.ListenAndServe())
33 | }
34 |
--------------------------------------------------------------------------------
/python/config.py:
--------------------------------------------------------------------------------
1 | import json
2 | import dateutil.parser
3 | import pprint
4 | from typing import Dict
5 |
6 |
7 | def parse(data: Dict, indent = 0) -> Dict:
8 | # where possible, parse time
9 | for key, val in data.items():
10 | if type(val) is dict:
11 | data[key] = parse(val, indent + 2)
12 | # noinspection PyBroadException
13 | try:
14 | parsed_time = dateutil.parser.isoparse(val)
15 | data[key] = parsed_time
16 | print(f'{" " * indent}Parsed "{key}" as datetime in ISO8601 format')
17 | except:
18 | ...
19 | # print(f'Config parsed as:\n{pprint.pformat(data)}')
20 | return data
21 |
22 |
23 | def parse_config(path: str) -> Dict:
24 | with open(path) as f:
25 | data = json.load(f)
26 | return parse(data)
27 |
28 |
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.function.automated_ratio.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION statistics.automated_ratio
2 | (
3 | _start_date DATE DEFAULT current_date - interval '7 years',
4 | _end_date DATE DEFAULT current_date
5 | )
6 | RETURNS TABLE (
7 | automated_bookings bigint,
8 | manual_bookings bigint
9 | ) AS
10 | $$
11 | BEGIN
12 | RETURN QUERY
13 | SELECT (SELECT COUNT(*)
14 | FROM booking.identifier b
15 | WHERE (b.start::date BETWEEN _start_date AND _end_date) AND b.booked = TRUE AND b.automated = TRUE
16 | ) AS automated_bookings,
17 | (SELECT COUNT(*)
18 | FROM booking.identifier b
19 | WHERE (b.start::date BETWEEN _start_date AND _end_date) AND b.booked = TRUE AND b.automated = FALSE
20 | ) AS manual_bookings;
21 |
22 | RETURN;
23 | END
24 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/python/scheduler-call.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from tracemalloc import start
3 | from turtle import st
4 | from typing import Dict
5 | import requests
6 |
7 | def retrieve_rooms_in_building(start: datetime.datetime) -> int:
8 | body: Dict[str] = {
9 | "start_date": start.replace(tzinfo=None).isoformat() + "Z"
10 | }
11 | resp = requests.post("http://localhost:8100/api/scheduler/execute", json=body)
12 | return resp.status_code
13 |
14 | start_date: datetime.datetime = datetime.datetime.now()
15 | start_date = start_date - datetime.timedelta(days=100)
16 | i = 0
17 | while start_date <= (datetime.datetime.now() - datetime.timedelta(days=40)):
18 | print(i, ": ", start_date)
19 | start_date += datetime.timedelta(days=7)
20 | resp = retrieve_rooms_in_building(start_date)
21 | print("RESPONSE:", resp)
22 | i += 1
--------------------------------------------------------------------------------
/db/sql/permission/permission.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION permission.identifier_store(
2 | _permission_id uuid, -- NULLABLE, If supplied try update else insert
3 | _permission_id_type permission.id_type,
4 | _permission_type permission.type,
5 | _permission_category permission.category,
6 | _permission_tenant permission.tenant,
7 | _permission_tenant_id uuid
8 | )
9 | RETURNS uuid AS
10 | $$
11 | DECLARE
12 | __id uuid;
13 | BEGIN
14 | INSERT INTO permission.identifier(permission_id, permission_id_type, permission_type, permission_category, permission_tenant, permission_tenant_id)
15 | VALUES (_permission_id, _permission_id_type, _permission_type, _permission_category, _permission_tenant, _permission_tenant_id)
16 | RETURNING identifier.id INTO __id;
17 |
18 | RETURN __id;
19 | END
20 | $$ LANGUAGE plpgsql;
21 |
--------------------------------------------------------------------------------
/db/sql/booking/booking.meeting_room.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION booking.meeting_room_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | booking_id uuid,
7 | team_id uuid,
8 | role_id uuid,
9 | additional_attendess INT,
10 | desks_attendees BOOLEAN,
11 | desks_additional_attendees BOOLEAN
12 | ) AS
13 | $$
14 | BEGIN
15 |
16 | IF NOT EXISTS (
17 | SELECT 1
18 | FROM booking.meeting_room as b
19 | WHERE b.id = _id
20 | FOR UPDATE
21 | ) THEN
22 | RAISE EXCEPTION 'invalid_meeting_room_boooking'
23 | USING HINT = 'Please check the provided meeting_room_booking id parameter';
24 | END IF;
25 |
26 | RETURN QUERY
27 | DELETE FROM booking.meeting_room as a WHERE a.id = _id
28 | RETURNING *;
29 |
30 | END
31 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/role/role.identifier.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.identifier_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _name VARCHAR(256),
4 | _color VARCHAR(256),
5 | _lead_id uuid DEFAULT NULL
6 | )
7 | RETURNS uuid AS
8 | $$
9 | DECLARE
10 | __id uuid;
11 | BEGIN
12 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM role.identifier WHERE id = _id)) THEN
13 | UPDATE role.identifier
14 | SET name = _name,
15 | color = _color,
16 | lead_id = _lead_id
17 | WHERE id = _id
18 | RETURNING identifier.id INTO __id;
19 | ELSE
20 | INSERT INTO role.identifier(id, name, color, lead_id)
21 | VALUES (COALESCE(_id, uuid_generate_v4()), _name, _color, _lead_id)
22 | RETURNING identifier.id INTO __id;
23 | END IF;
24 | RETURN __id;
25 | END
26 | $$ LANGUAGE plpgsql;
27 |
--------------------------------------------------------------------------------
/.github/workflows/react.yml:
--------------------------------------------------------------------------------
1 | name: 'React Test'
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build:
7 | name: Build Test
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [ 12, 14, 16 ]
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Set up Node.js 17.x
18 | uses: actions/setup-node@v3.1.1
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 |
22 | - name: npm ci
23 | working-directory: ./client/web/example
24 | run: |
25 | npm ci
26 | npm install konva canvas
27 |
28 | - name: Build
29 | working-directory: ./client/web/example
30 | run: npm run build --if-present
31 |
32 | - name: Test
33 | working-directory: ./client/web/example
34 | run: npm test
35 |
--------------------------------------------------------------------------------
/db/sql/team/team.association.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.association_remove(
2 | _team_id uuid,
3 | _team_id_association uuid
4 | )
5 | RETURNS TABLE (
6 | team_id uuid,
7 | team_id_association uuid
8 | ) AS
9 | $$
10 | BEGIN
11 |
12 | IF NOT EXISTS (
13 | SELECT 1
14 | FROM team.association as b
15 | WHERE b.team_id = _team_id
16 | AND b.team_id_association = _team_id_association
17 | FOR UPDATE
18 | ) THEN
19 | RAISE EXCEPTION 'invalid_team_association'
20 | USING HINT = 'Please check the provided team and association parameter';
21 | END IF;
22 |
23 | RETURN QUERY
24 | DELETE FROM team.association as a
25 | WHERE a.team_id = _team_id
26 | AND a.team_id_association = _team_id_association
27 | RETURNING *;
28 |
29 | END
30 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.function.utilisation.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION statistics.utilisation
2 | (
3 | _start_date DATE DEFAULT current_date - interval '7 days',
4 | _end_date DATE DEFAULT current_date
5 | )
6 | RETURNS TABLE (
7 | resource_id uuid,
8 | utilisation_percent float
9 | ) AS
10 | $$
11 | DECLARE
12 | num_days INTEGER;
13 | BEGIN
14 | RETURN QUERY
15 | SELECT r.id AS resource_id, COALESCE(((SELECT COUNT(DISTINCT b.start::date)
16 | FROM booking.identifier b
17 | WHERE b.resource_id = r.id AND (b.start::date BETWEEN _start_date AND _end_date) AND b.booked = TRUE
18 | GROUP BY b.resource_id
19 | )/(_end_date::date - _start_date::date + 1)::float)*100, 0)::float AS utilisation_percent
20 | FROM resource.identifier r;
21 |
22 | RETURN;
23 | END
24 | $$ LANGUAGE plpgsql;
25 |
--------------------------------------------------------------------------------
/db/sql/user/user.credential.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".credential_store (
2 | _id VARCHAR(256),
3 | _secret VARCHAR(256),
4 | _identifier VARCHAR(256)
5 | )
6 | RETURNS BOOLEAN AS
7 | $$
8 | BEGIN
9 | IF EXISTS(SELECT 1 FROM "user"."credential" WHERE id = _id) THEN
10 | UPDATE "user".credential
11 | SET secret = CRYPT(_secret, GEN_SALT('bf'))::VARCHAR(256),
12 | identifier = _identifier,
13 | active = TRUE,
14 | failed_attempts = 0,
15 | last_accessed = now() AT TIME ZONE 'uct'
16 | WHERE id = _id;
17 | ELSE
18 | INSERT INTO "user"."credential" (id, secret, identifier, active, failed_attempts)
19 | VALUES (_id, CRYPT(_secret, GEN_SALT('bf'))::VARCHAR(256), _identifier, TRUE, 0);
20 | END IF;
21 | RETURN TRUE;
22 | END
23 | $$ LANGUAGE plpgsql;
24 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.identifier.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | room_id uuid,
7 | name VARCHAR(256),
8 | xcoord float,
9 | ycoord float,
10 | width float,
11 | height float,
12 | rotation float,
13 | resource_type resource.type,
14 | date_created TIMESTAMP,
15 | decorations JSON
16 | ) AS
17 | $$
18 | BEGIN
19 |
20 | IF NOT EXISTS (
21 | SELECT 1
22 | FROM resource.identifier as b
23 | WHERE b.id = _id
24 | FOR UPDATE
25 | ) THEN
26 | RAISE EXCEPTION 'invalid_resource'
27 | USING HINT = 'Please check the provided resource id parameter';
28 | END IF;
29 |
30 | RETURN QUERY
31 | DELETE FROM resource.identifier as a WHERE a.id = _id
32 | RETURNING *;
33 |
34 | END
35 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/client/web/example/src/components/Logout/LogoutButton.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { UserContext } from '../../App'
3 | import "../../App.css"
4 | import Button from 'react-bootstrap/Button'
5 | import { useNavigate } from 'react-router-dom'
6 |
7 | const LogoutButton = () =>{
8 | const {userData,setUserData} = useContext(UserContext);
9 | const navigate = useNavigate();
10 | // const location = useLocation();
11 |
12 | let handleSubmit = async(e) =>{
13 | e.preventDefault();
14 | setUserData(null);
15 | localStorage.removeItem("auth_data");
16 | console.log(userData);
17 | navigate("/login");
18 | }
19 |
20 | return (
21 |
22 | )
23 | }
24 |
25 | export default LogoutButton;
--------------------------------------------------------------------------------
/db/sql/booking/booking.identifier.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION booking.identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | user_id uuid,
7 | resource_type resource.type,
8 | resource_preference_id uuid,
9 | resource_id uuid,
10 | start TIMESTAMP,
11 | "end" TIMESTAMP,
12 | booked BOOLEAN,
13 | automated BOOLEAN,
14 | date_created TIMESTAMP,
15 | dependent uuid
16 | ) AS
17 | $$
18 | BEGIN
19 |
20 | IF NOT EXISTS (
21 | SELECT 1
22 | FROM booking.identifier as b
23 | WHERE b.id = _id
24 | FOR UPDATE
25 | ) THEN
26 | RAISE EXCEPTION 'invalid_booking'
27 | USING HINT = 'Please check the provided booking id parameter';
28 | END IF;
29 |
30 | RETURN QUERY
31 | DELETE FROM booking.identifier as a WHERE a.id = _id
32 | RETURNING *;
33 |
34 | END
35 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/resource/resource.building.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.building_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _name VARCHAR(256),
4 | _location VARCHAR(256),
5 | _dimension VARCHAR(256)
6 | )
7 | RETURNS uuid AS
8 | $$
9 | DECLARE
10 | __id uuid;
11 | BEGIN
12 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM resource.building WHERE id = _id)) THEN
13 | UPDATE resource.building
14 | SET name = _name,
15 | location = _location,
16 | dimension = _dimension
17 | WHERE id = _id
18 | RETURNING building.id INTO __id;
19 | ELSE
20 | INSERT INTO resource.building(id, name, location, dimension)
21 | VALUES (COALESCE(_id, uuid_generate_v4()), _name, _location, _dimension)
22 | RETURNING building.id INTO __id;
23 | END IF;
24 | RETURN __id;
25 | END
26 | $$ LANGUAGE plpgsql;
27 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.association.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_association_remove(
2 | _room_id uuid,
3 | _room_id_association uuid
4 | )
5 | RETURNS TABLE (
6 | room_id uuid,
7 | room_id_association uuid
8 | ) AS
9 | $$
10 | BEGIN
11 |
12 | IF NOT EXISTS (
13 | SELECT 1
14 | FROM resource.room_association as b
15 | WHERE b.room_id = _room_id
16 | AND b.room_id_association = _room_id_association
17 | FOR UPDATE
18 | ) THEN
19 | RAISE EXCEPTION 'invalid_room_association'
20 | USING HINT = 'Please check the provided room association parameters';
21 | END IF;
22 |
23 | RETURN QUERY
24 | DELETE FROM resource.room_association as a
25 | WHERE a.room_id = _room_id
26 | AND a.room_id_association = _room_id_association
27 | RETURNING *;
28 |
29 | END
30 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/db/sql/permission/permission.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION permission.identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | permission_id uuid,
7 | permission_id_type permission.id_type,
8 | permission_type permission.type,
9 | permission_category permission.category,
10 | permission_tenant permission.tenant,
11 | permission_tenant_id uuid,
12 | date_added TIMESTAMP
13 | ) AS
14 | $$
15 | BEGIN
16 |
17 | IF NOT EXISTS (
18 | SELECT 1
19 | FROM permission.identifier as b
20 | WHERE b.id = _id
21 | FOR UPDATE
22 | ) THEN
23 | RAISE EXCEPTION 'invalid_permission'
24 | USING HINT = 'Please check the provided permission id parameter';
25 | END IF;
26 |
27 | RETURN QUERY
28 | DELETE FROM permission.identifier as a WHERE a.id = _id
29 | RETURNING *;
30 |
31 | END
32 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/lib/test_setup/db_access.go:
--------------------------------------------------------------------------------
1 | package test_setup
2 |
3 | import (
4 | "api/db"
5 | "testing"
6 | )
7 |
8 | func ConnectDB(t *testing.T) error {
9 | t.Setenv("DATABASE_DSN", "host=127.0.0.1 port=5432 user=admin dbname=arche sslmode=disable")
10 | t.Setenv("DATABASE_MAX_IDLE_CONNECTIONS", "5")
11 | t.Setenv("DATABASE_MAX_OPEN_CONNECTIONS", "25")
12 | t.Setenv("DATABASE_URL", "postgresql://admin:admin@localhost:5432/db?schema=public")
13 | // testdb := SetupTest(t)
14 | // defer dtdb.StopTestDbWithTest(testdb, t, false)
15 | err := db.RegisterAccess()
16 | if err != nil {
17 | t.Fatal(err)
18 | return err
19 | }
20 | _, err = db.Open()
21 | if err != nil {
22 | t.Fatal(err)
23 | return err
24 | }
25 | return nil
26 | }
27 |
28 | func DisconnectDB(t *testing.T) error {
29 | err := db.UnregisterAccess()
30 | if err != nil {
31 | t.Log(err)
32 | return err
33 | }
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/db/sql/statistics/statistics.function.yearly_utilisation.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION statistics.yearly_utilisation ()
2 | RETURNS TABLE (
3 | month_ DATE,
4 | utilisation_percent float
5 | ) AS
6 | $$
7 | DECLARE
8 | _start_date DATE;
9 | _end_date DATE;
10 | BEGIN
11 | _start_date := DATE_TRUNC('month', (current_date - interval '11 months'));
12 | _end_date := DATE_TRUNC('month', (current_date + interval '1 month'));
13 | RETURN QUERY
14 | SELECT DATE_TRUNC('month', b.start::date)::date, statistics.average_utilisation(DATE_TRUNC('month', b.start::date)::date, (DATE_TRUNC('month', b.start::date) + interval '1 month')::date)
15 | FROM booking.identifier b
16 | GROUP BY DATE_TRUNC('month', b.start::date)
17 | HAVING DATE_TRUNC('month', b.start::date)::date BETWEEN (current_date-interval '1 year') AND current_date
18 | ORDER BY DATE_TRUNC('month', b.start::date) ASC;
19 | RETURN;
20 | END
21 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/go/api/Dockerfile:
--------------------------------------------------------------------------------
1 | # build image
2 | FROM golang:1.18.6-alpine3.16 AS builder
3 |
4 | WORKDIR /opt/arche-api
5 |
6 | RUN apk add git
7 | RUN wget -O - -q https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s v2.13.1
8 |
9 | ADD ./api/go.mod ./api/
10 | ADD ./api/go.sum ./api/
11 | ADD ./lib/go.mod ./lib/
12 | ADD ./lib/go.sum ./lib/
13 | WORKDIR /opt/arche-api/api
14 | RUN go mod download
15 |
16 | WORKDIR /opt/arche-api
17 | ADD . .
18 |
19 | WORKDIR /opt/arche-api/api
20 |
21 | RUN go build -o arche-api
22 | RUN ../bin/gosec ./...
23 |
24 | # final image
25 | FROM alpine:3.16
26 | RUN apk add bash
27 | RUN adduser --disabled-password -h /opt/arche-api -G tty --shell /bin/bash arche-api
28 | USER arche-api
29 | WORKDIR /opt/arche-api
30 | COPY --chown=arche-api:root --from=builder /opt/arche-api/api/arche-api ./
31 | RUN chmod 755 /opt/arche-api
32 | EXPOSE 8080
33 | ENTRYPOINT ["./arche-api", "--config", "/run/secrets/config.json", "--port", "8080"]
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: 'Go API Test'
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 |
12 | - name: Set up Go 1.18
13 | uses: actions/setup-go@v3
14 | with:
15 | go-version: 1.18
16 |
17 | - name: Build
18 | working-directory: ./go/api
19 | run: go build -v ./...
20 |
21 | - name: Start containers
22 | working-directory: ./
23 | run: docker-compose -f docker-compose.integration-test.yml up --build -d
24 |
25 | - name: Sleep for 60 seconds and wait for containers to start
26 | uses: jakejarvis/wait-action@master
27 | with:
28 | time: '60s'
29 |
30 | # - name: Wait for containers startup
31 | # run: sleep 1m
32 | # shell: bash
33 |
34 | - name: Test
35 | working-directory: ./go/api
36 | run: go test -cover -v ./...
37 |
--------------------------------------------------------------------------------
/db/sql/user/user.identifier.function.remove.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".identifier_remove(
2 | _id uuid
3 | )
4 | RETURNS TABLE (
5 | id uuid,
6 | identifier VARCHAR(256),
7 | first_name VARCHAR(256),
8 | last_name VARCHAR(256),
9 | email VARCHAR(256),
10 | picture TEXT,
11 | date_created TIMESTAMP,
12 | work_from_home BOOLEAN,
13 | parking parking.type,
14 | office_days INTEGER,
15 | preferred_start_time TIME WITHOUT TIME ZONE,
16 | preferred_end_time TIME WITHOUT TIME ZONE,
17 | building_id uuid
18 | ) AS
19 | $$
20 | BEGIN
21 |
22 | IF NOT EXISTS (
23 | SELECT 1
24 | FROM "user".identifier as b
25 | WHERE b.id = _id
26 | FOR UPDATE
27 | ) THEN
28 | RAISE EXCEPTION 'invalid_user'
29 | USING HINT = 'Please check the provided user id parameter';
30 | END IF;
31 |
32 | RETURN QUERY
33 | DELETE FROM "user".identifier as a WHERE a.id = _id
34 | RETURNING *;
35 |
36 | END
37 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------
/client/web/example/src/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | import { getAnalytics } from "firebase/analytics";
4 | import { getStorage } from 'firebase/storage';
5 | // TODO: Add SDKs for Firebase products that you want to use
6 | // https://firebase.google.com/docs/web/setup#available-libraries
7 |
8 | // Your web app's Firebase configuration
9 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional
10 | const firebaseConfig =
11 | {
12 | apiKey: "AIzaSyDAOgpsLn2edRfUBSf7pTfXhOk_EMbJ6Is",
13 | authDomain: "arche-6bd39.firebaseapp.com",
14 | projectId: "arche-6bd39",
15 | storageBucket: "arche-6bd39.appspot.com",
16 | messagingSenderId: "793359777992",
17 | appId: "1:793359777992:web:df040d93a1719df41265fd",
18 | measurementId: "G-JGMC21VFLM"
19 | };
20 |
21 | // Initialize Firebase
22 | const app = initializeApp(firebaseConfig);
23 | const analytics = getAnalytics(app);
24 | export const storage = getStorage(app);
--------------------------------------------------------------------------------
/google_api/google_api/go.mod:
--------------------------------------------------------------------------------
1 | module quickstart
2 |
3 | go 1.18
4 |
5 | require (
6 | cloud.google.com/go/compute v1.7.0 // indirect
7 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
8 | github.com/golang/protobuf v1.5.2 // indirect
9 | github.com/google/uuid v1.3.0 // indirect
10 | github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
11 | github.com/googleapis/gax-go/v2 v2.4.0 // indirect
12 | go.opencensus.io v0.23.0 // indirect
13 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
14 | golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
15 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
16 | golang.org/x/text v0.3.7 // indirect
17 | google.golang.org/api v0.96.0 // indirect
18 | google.golang.org/appengine v1.6.7 // indirect
19 | google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
20 | google.golang.org/grpc v1.47.0 // indirect
21 | google.golang.org/protobuf v1.28.0 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Navbar/navbar.module.css:
--------------------------------------------------------------------------------
1 | .navbarContainer
2 | {
3 | position: absolute;
4 | background-color: transparent;
5 | color: #374146;
6 |
7 | top: 0;
8 |
9 | padding-left: 2vw;
10 |
11 | text-align: left;
12 |
13 | width: 15vw;
14 | min-height: 100vh;
15 | }
16 |
17 | .logoContainer
18 | {
19 | margin-top: 1vh;
20 | font-size: 3vw;
21 | height: 8vh;
22 | letter-spacing: 0.8vw;
23 | }
24 |
25 | .navlinkContainer
26 | {
27 | font-size: 1vw;
28 | }
29 |
30 | .navlink
31 | {
32 | margin-top: 5vh;
33 | letter-spacing: 0.5vh;
34 | }
35 |
36 | .navlink:hover
37 | {
38 | cursor: pointer;
39 | color: #09a2fb;
40 | }
41 |
42 | .navlinkDropdownContainer
43 | {
44 | display: none;
45 | margin-left: 2.5vw;
46 | font-size: 0.9vw;
47 | }
48 |
49 | .navlinkDropdown
50 | {
51 | margin-top: 2vh;
52 | letter-spacing: 0.5vh;
53 | }
54 |
55 | .navlinkDropdown:hover
56 | {
57 | cursor: pointer;
58 | color: #09a2fb;
59 | }
--------------------------------------------------------------------------------
/go/scheduler/data/utils.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | func TimeInIntervalInclusive(check time.Time, start time.Time, end time.Time) bool {
8 | return (check.After(start) || start == check) && (check.Before(end) || end == check)
9 | }
10 |
11 | // Returns true if the resource is not allocated in a certain interval
12 | func (schedulerData *SchedulerData) ResourceAvailable(resource *Resource, from time.Time, to time.Time) bool {
13 | for _, booking := range *schedulerData.CurrentBookings {
14 | if booking.ResourceId == resource.Id {
15 | if TimeInIntervalInclusive(*booking.Start, from, to) || TimeInIntervalInclusive(*booking.End, from, to) {
16 | return false
17 | }
18 | }
19 | }
20 | return true
21 | }
22 |
23 | func (schedulerData *SchedulerData) TeamSize(teamId string) int {
24 | return len(schedulerData.TeamsMap[teamId].UserIds)
25 | }
26 |
27 | func (schedulerData *SchedulerData) RoleSize(RoleId string) int {
28 | return len(schedulerData.RolesMap[RoleId].UserIds)
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/coverage_go.yml:
--------------------------------------------------------------------------------
1 | name: 'Test Coverage Backend'
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build_and_test:
7 | name: Build and Test
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v3
12 | with:
13 | token: ${{ secrets.MY_PAT }}
14 | fetch-depth: 2
15 |
16 | - name: Start containers
17 | working-directory: ./
18 | run: docker-compose -f docker-compose.integration-test.yml up --build -d
19 |
20 | - name: Sleep for 60 seconds and wait for containers to start
21 | uses: jakejarvis/wait-action@master
22 | with:
23 | time: '60s'
24 |
25 | - name: Set up Go 1.18
26 | uses: actions/setup-go@v3
27 | with:
28 | go-version: 1.18
29 |
30 | - name: Get Coverage
31 | working-directory: ./go/api
32 | run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
33 |
34 | - name: Upload Coverage
35 | uses: codecov/codecov-action@v2
36 |
--------------------------------------------------------------------------------
/client/web/example/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { UserContext } from '../App';
3 | import ProfileBar from '../components/Navbar/ProfileBar.js';
4 | import Navbar from '../components/Navbar/Navbar.js';
5 | import NavbarAdmin from '../components/Navbar/NavbarAdmin';
6 | import Map from '../components/Map/Map';
7 |
8 | const Home = () =>
9 | {
10 | const {userData} = useContext(UserContext);
11 |
12 | const showNavbar = () =>
13 | {
14 | if(!userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default Home
--------------------------------------------------------------------------------
/go/lib/collectionutils/maputils_test.go:
--------------------------------------------------------------------------------
1 | package collectionutils
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestMapHasKey(t *testing.T) {
10 | type args[K comparable, V any] struct {
11 | _map map[K]V
12 | key K
13 | }
14 | tests := []struct {
15 | name string
16 | args args[string, any]
17 | want bool
18 | }{
19 | {
20 | name: "Key that IS contained",
21 | args: args[string, any]{
22 | _map: map[string]any{
23 | "apple": 2,
24 | "orange": "3",
25 | "ananas": true,
26 | },
27 | key: "ananas",
28 | },
29 | want: true,
30 | },
31 | {
32 | name: "Key that IS NOT contained",
33 | args: args[string, any]{
34 | _map: map[string]any{
35 | "apple": 2,
36 | "orange": "3",
37 | "ananas": true,
38 | },
39 | key: "tomato",
40 | },
41 | want: false,
42 | },
43 | }
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.want, MapHasKey(tt.args._map, tt.args.key), "Expected %v", tt.want)
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/go/scheduler/Dockerfile:
--------------------------------------------------------------------------------
1 | # build image
2 | FROM golang:1.18.6-alpine3.16 AS builder
3 |
4 | WORKDIR /opt/arche-scheduler
5 |
6 | RUN apk add git
7 | RUN wget -O - -q https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s v2.13.1
8 |
9 | ADD ./scheduler/go.mod ./scheduler/
10 | ADD ./scheduler/go.sum ./scheduler/
11 | ADD ./lib/go.mod ./lib/
12 | ADD ./lib/go.sum ./lib/
13 | WORKDIR /opt/arche-scheduler/scheduler
14 | RUN go mod download
15 |
16 | WORKDIR /opt/arche-scheduler
17 | ADD . .
18 |
19 | WORKDIR /opt/arche-scheduler/scheduler
20 |
21 | RUN go build -o arche-scheduler
22 | RUN ../bin/gosec ./...
23 |
24 | # final image
25 | FROM alpine:3.16
26 | RUN apk add bash
27 | RUN adduser --disabled-password -h /opt/arche-scheduler -G tty --shell /bin/bash arche-scheduler
28 | USER arche-scheduler
29 | WORKDIR /opt/arche-scheduler
30 | COPY --chown=arche-scheduler:root --from=builder /opt/arche-scheduler/scheduler/arche-scheduler ./
31 | RUN chmod 755 /opt/arche-scheduler
32 | EXPOSE 8080
33 | ENTRYPOINT ["./arche-scheduler", "--config", "/run/secrets/config.json", "--port", "8080"]
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _building_id uuid,
4 | _name VARCHAR(256),
5 | _xcoord float,
6 | _ycoord float,
7 | _zcoord float,
8 | _dimension VARCHAR(256)
9 | )
10 | RETURNS uuid AS
11 | $$
12 | DECLARE
13 | __id uuid;
14 | BEGIN
15 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM resource.room WHERE id = _id)) THEN
16 | UPDATE resource.room
17 | SET name = _name,
18 | building_id = _building_id,
19 | xcoord = _xcoord,
20 | ycoord = _ycoord,
21 | zcoord = _zcoord,
22 | dimension = _dimension
23 | WHERE id = _id
24 | RETURNING room.id INTO __id;
25 | ELSE
26 | INSERT INTO resource.room(id, building_id, name, xcoord, ycoord, zcoord, dimension)
27 | VALUES (COALESCE(_id, uuid_generate_v4()), _building_id, _name, _xcoord, _ycoord, _zcoord, _dimension)
28 | RETURNING room.id INTO __id;
29 | END IF;
30 | RETURN __id;
31 | END
32 | $$ LANGUAGE plpgsql;
33 |
--------------------------------------------------------------------------------
/db/sql/team/team.identifier.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.identifier_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _name VARCHAR(256),
4 | _color VARCHAR(256),
5 | _capacity INT,
6 | _picture TEXT,
7 | _priority INT DEFAULT NULL,
8 | _team_lead_id uuid DEFAULT NULL
9 | )
10 | RETURNS uuid AS
11 | $$
12 | DECLARE
13 | __id uuid;
14 | BEGIN
15 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM team.identifier WHERE id = _id)) THEN
16 | UPDATE team.identifier
17 | SET name = _name,
18 | color = _color,
19 | capacity = _capacity,
20 | picture = _picture,
21 | team_lead_id = _team_lead_id,
22 | priority = _priority
23 | WHERE id = _id
24 | RETURNING identifier.id INTO __id;
25 | ELSE
26 | INSERT INTO team.identifier(id, name, color, capacity, picture, priority, team_lead_id)
27 | VALUES (COALESCE(_id, uuid_generate_v4()), _name, _color, _capacity, _picture, _priority, _team_lead_id)
28 | RETURNING identifier.id INTO __id;
29 | END IF;
30 | RETURN __id;
31 | END
32 | $$ LANGUAGE plpgsql;
33 |
--------------------------------------------------------------------------------
/client/web/example/src/pages/OfficeCreator.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { UserContext } from '../App';
3 | import ProfileBar from '../components/Navbar/ProfileBar.js';
4 | import Navbar from '../components/Navbar/Navbar.js';
5 | import NavbarAdmin from '../components/Navbar/NavbarAdmin';
6 | import Creator from '../components/Map/Creator';
7 |
8 | const OfficeCreator = () =>
9 | {
10 | const {userData} = useContext(UserContext);
11 |
12 | const showNavbar = () =>
13 | {
14 | if(!userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default OfficeCreator
--------------------------------------------------------------------------------
/client/web/example/src/pages/Profile.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import Navbar from '../components/Navbar/Navbar.js';
3 | import { UserContext } from '../App';
4 | import ProfileBar from '../components/Navbar/ProfileBar';
5 | import NavbarAdmin from '../components/Navbar/NavbarAdmin.js';
6 | import ProfileComponent from '../components/Profile/ProfileComponent.js';
7 |
8 | function Profile()
9 | {
10 | const {userData, setUserData} = useContext(UserContext)
11 |
12 | const showNavbar = () =>
13 | {
14 | if(!userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default Profile
--------------------------------------------------------------------------------
/client/web/example/src/components/Resources/resources.module.css:
--------------------------------------------------------------------------------
1 | .form
2 | {
3 | padding-top: 2vh;
4 |
5 | width: 20vw;
6 |
7 | margin-left: 5vw;
8 |
9 | text-align: left;
10 |
11 | color: #374146;
12 | }
13 |
14 | .formGroup
15 | {
16 | margin-bottom: 2vh;
17 | }
18 |
19 | .formLabel
20 | {
21 | font-size: 1vw;
22 | letter-spacing: 0.1vw;
23 | }
24 |
25 | .formInput
26 | {
27 | margin-top: 1vh;
28 | padding-left: 1vw;
29 | padding-top: 1vh;
30 | padding-bottom: 1vh;
31 | width: 20vw;
32 |
33 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
34 |
35 | border-style: none;
36 | border-radius: 1.5vh;
37 | outline: none;
38 |
39 | font-size: 0.8vw;
40 | }
41 |
42 | .formInput:focus
43 | {
44 | outline: none;
45 | }
46 |
47 |
48 | .submit
49 | {
50 | width: 10vw;
51 | height: 4vh;
52 |
53 | margin-left: 5vw;
54 |
55 | border-radius: 1vh;
56 | border-style: none;
57 |
58 | font-size: 1vw;
59 |
60 | background-color: #09a2fb;
61 | color: white;
62 | }
63 |
64 | .submit:hover
65 | {
66 | cursor: pointer;
67 | background-color: #03639b;
68 | }
--------------------------------------------------------------------------------
/client/web/example/src/pages/Calendar.js:
--------------------------------------------------------------------------------
1 | import Navbar from '../components/Navbar/Navbar.js'
2 | import ProfileBar from '../components/Navbar/ProfileBar.js';
3 | import NavbarAdmin from '../components/Navbar/NavbarAdmin.js';
4 | import CalendarComponent from '../components/Calendar/CalendarComponent.js';
5 | import { UserContext } from '../App.js';
6 | import { useContext } from 'react';
7 |
8 | const Calendar = () =>
9 | {
10 | const {userData} = useContext(UserContext);
11 |
12 | const showNavbar = () =>
13 | {
14 | if(userData !== null && !userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default Calendar
--------------------------------------------------------------------------------
/client/web/example/src/pages/Statistics.js:
--------------------------------------------------------------------------------
1 | import ProfileBar from '../components/Navbar/ProfileBar.js';
2 | import Navbar from '../components/Navbar/Navbar.js';
3 | import NavbarAdmin from '../components/Navbar/NavbarAdmin.js';
4 | import { useContext } from 'react';
5 | import { UserContext } from '../App.js';
6 | import StatisticsComponent from '../components/StatisticsComponent/StatisticsComponent.js';
7 |
8 | const Statistics = () =>
9 | {
10 | const {userData} = useContext(UserContext);
11 |
12 | const showNavbar = () =>
13 | {
14 | if(userData !== null && !userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default Statistics
--------------------------------------------------------------------------------
/client/web/example/src/pages/BookingsDesk.js:
--------------------------------------------------------------------------------
1 | import Navbar from "../components/Navbar/Navbar.js"
2 | import DeskBooking from "../components/BookingForm/DeskBooking"
3 | import { useContext, useRef } from "react"
4 | import ProfileBar from "../components/Navbar/ProfileBar.js";
5 | import NavbarAdmin from "../components/Navbar/NavbarAdmin.js";
6 | import { UserContext } from '../App';
7 |
8 | const BookingsDesk = () =>
9 | {
10 | const deskRef = useRef(null);
11 | const {userData} = useContext(UserContext);
12 |
13 | const showNavbar = () =>
14 | {
15 | if(!userData.user_identifier.includes("admin"))
16 | {
17 | return ;
18 | }
19 | else
20 | {
21 | return ;
22 | }
23 | };
24 |
25 | return (
26 |
27 |
28 |
29 | {showNavbar()}
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default BookingsDesk
--------------------------------------------------------------------------------
/db/sql/team/team.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS team;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3 |
4 | CREATE TABLE IF NOT EXISTS team.identifier (
5 | id uuid DEFAULT uuid_generate_v4(),
6 | name VARCHAR(256),
7 | color VARCHAR(256),
8 | capacity INT,
9 | picture TEXT,
10 | priority INT DEFAULT 0, -- 0 for low, 1 for medium and 2 for high
11 | team_lead_id uuid REFERENCES "user".identifier(id) ON DELETE SET NULL,
12 | date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
13 |
14 | PRIMARY KEY (id)
15 | );
16 |
17 | CREATE TABLE IF NOT EXISTS team.user (
18 | team_id uuid NOT NULL REFERENCES team.identifier(id) ON DELETE CASCADE,
19 | user_id uuid NOT NULL REFERENCES "user".identifier(id) ON DELETE CASCADE,
20 | date_added TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
21 |
22 | PRIMARY KEY (team_id, user_id)
23 | );
24 |
25 | CREATE TABLE IF NOT EXISTS team.association (
26 | team_id uuid NOT NULL REFERENCES team.identifier(id) ON DELETE CASCADE,
27 | team_id_association uuid NOT NULL REFERENCES team.identifier(id) ON DELETE CASCADE,
28 |
29 | PRIMARY KEY (team_id, team_id_association)
30 | );
--------------------------------------------------------------------------------
/db/sql/user/user.credential.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".credential_find(
2 | _id VARCHAR(256) DEFAULT NULL,
3 | _secret VARCHAR(256) DEFAULT NULL,
4 | _identifier VARCHAR(256) DEFAULT NULL,
5 | _type "user".credential_type DEFAULT NULL,
6 | _active BOOLEAN DEFAULT NULL,
7 | _failed_attempts INT DEFAULT NULL,
8 | _last_accessed TIMESTAMP DEFAULT NULL
9 | )
10 | RETURNS TABLE (
11 | id VARCHAR(256),
12 | secret VARCHAR(256),
13 | identifier VARCHAR(256),
14 | "type" "user".credential_type,
15 | active BOOLEAN,
16 | failed_attempts INT,
17 | last_accessed TIMESTAMP
18 | ) AS
19 | $$
20 | BEGIN
21 | RETURN QUERY
22 | SELECT i.id, i.secret, i.identifier, i."type", i.active, i.failed_attempts, i.last_accessed
23 | FROM "user".credential as i
24 | WHERE (_id IS NULL OR i.id = _id)
25 | AND (_secret IS NULL OR CRYPT(_secret, i.secret) = i.secret)
26 | AND (_identifier IS NULL OR i.identifier = _identifier)
27 | AND (_type IS NULL OR i."type" = _type)
28 | AND (_active IS NULL OR i.active = _active)
29 | AND (_failed_attempts IS NULL OR i.failed_attempts = _failed_attempts)
30 | AND (_last_accessed IS NULL OR i.last_accessed >= _last_accessed);
31 | END
32 | $$ LANGUAGE plpgsql;
33 |
--------------------------------------------------------------------------------
/proxy/Arche-Server.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDPzCCAiegAwIBAgIUfUXw8Jg9QUS/mg3XZvwG02pxZcMwDQYJKoZIhvcNAQEL
3 | BQAwLzELMAkGA1UEBhMCWkExEDAOBgNVBAgMB0dhdXRlbmcxDjAMBgNVBAoMBUFy
4 | Y2hlMB4XDTIyMDkwNjE0MDk1MVoXDTIzMDkwNjE0MDk1MVowLzELMAkGA1UEBhMC
5 | WkExEDAOBgNVBAgMB0dhdXRlbmcxDjAMBgNVBAoMBUFyY2hlMIIBIjANBgkqhkiG
6 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWKRsIPbciAwdk/XiO8PzDIMomjcvTAfRN7h
7 | +wgVsV2WnbEzyrTn4MDytDDFBseAvgSuKGdfn3wSpJFQVnQ69dS3g8KF8bS2ZaSu
8 | E6BDQCxPFqk+MfNvXebdKdt4IstsX3ev+O/NyvrJ1Tz17dJJaBagkh2S463pWBTj
9 | e07z66keq0eYoirWYm6KJtDxnD7yrgukvufTAov2p7PGB9k1ci7nGuw2+BiLtbsp
10 | 8y9j7rmYJEVbQFbq6ELv1WU57B1rrleZo5OeV7MVhY/l9l8dnBpO3rEuJGd+pxwx
11 | bJ0ghEKM/QOqJovKMF85p+1zlv4yE7O77b+gpo4UIQxN47PnuQIDAQABo1MwUTAd
12 | BgNVHQ4EFgQUVAgKxj4G/7+lawR5Qcktnk85NaEwHwYDVR0jBBgwFoAUVAgKxj4G
13 | /7+lawR5Qcktnk85NaEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
14 | AQEANxSx6ScbLZBp1WynM8AWJNfGsHd9v+IXQi6v3xPWU0O3DMITeVdmdJnZDkzL
15 | fcybAb4eFE25wPjDOcAZ2TvwRSXWJ7tNVOdFre4rvVcJV5ia0OkN04Vs3/rDyQOi
16 | U6x8wv929T6H1Lhgq/g9Scj0ALrT8VwM3WKiOk6ckkvRoInZKEiK6xShJ5LSDrlh
17 | tJWAmfWi4fa6CBcXSUIoJK1g4+7w8VEWFl4eRJ/8UHMgoPjAGXQoiuzkwgiEGuDq
18 | TkgpYxiS0W2OJCrB373GM6ax1uol1QzCylbydtozUp6srWbKlpjqJKVllAopoO6R
19 | GPeqfrZH4h2PIhl0HQNEqJPuHw==
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Forms/FormField.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 |
4 | const FormField = ({type, label, fieldId, placeholder, required, children, validator, onStateChanged}) =>
5 | {
6 | const [stateValues, SetStateValues] = useState({value : '', dirty : false, errors : []});
7 |
8 | HasChanged = e =>
9 | {
10 | e.preventDefault();
11 |
12 | const { label, required = false, validator = f => f, onStateChanged = f => f } = this.props;
13 |
14 | const value = e.target.value;
15 | const isEmpty = value.length === 0;
16 | const requiredMissing = stateValues.dirty && required && isEmpty;
17 |
18 | let errors = [];
19 |
20 | if(requiredMissing)
21 | {
22 | errors = [...errors, `${label} is required`];
23 | }
24 | else if('function' === typeof validator)
25 | {
26 | try
27 | {
28 | validate(value);
29 | }
30 | catch(e)
31 | {
32 | errors = [...errors, e.message];
33 | }
34 | }
35 |
36 | SetStateValues(({dirty = false}) => ({value, errors, dirty: !dirty || dirty}), () => onStateChanged(stateValues));
37 | }
38 | }
39 |
40 | export default FormField;
--------------------------------------------------------------------------------
/client/web/example/src/components/Profile/UserListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MdEdit } from 'react-icons/md'
3 | import { MdAccountCircle } from 'react-icons/md'
4 | import { useNavigate } from 'react-router-dom'
5 |
6 | const UserListItem = ({id, name, email}) => {
7 | const navigate = useNavigate();
8 |
9 | let EditUser = async (e) =>
10 | {
11 | e.preventDefault();
12 | window.sessionStorage.setItem("UserID", id);
13 | window.sessionStorage.setItem("UserName", name);
14 | window.sessionStorage.setItem("UserEmail", email);
15 | navigate("/user-edit");
16 | }
17 |
18 | return (
19 |
30 | )
31 | }
32 |
33 | export default UserListItem
--------------------------------------------------------------------------------
/client/web/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | The example app was generated using `npm install -g create-react-app@3.4.1` followed by `npm init react-app example --use-npm`
4 |
5 | More information can be found at: https://mherman.org/blog/dockerizing-a-react-app/
6 |
7 | TODO [KP]: The example app should have hot reloading but it doesn't seem to work please check `./docker-compose.yml` for all the attempted fixes also see: https://github.com/facebook/create-react-app/issues/9904 to track this issue.
8 |
9 | # React Testing
10 | ## File Naming
11 | Files that are used for testing in Go should be named in the following way:
12 | > the name of the file or module being tested, followed by a .test.js suffix, e.g. A test for a file, component.js, would be named, component.test.js
13 |
14 |
15 | ## File structure
16 | The test files should be kept in the same directory as the code that they are testing, hence the following example structure must be followed
17 | ```bash
18 | .
19 | ├── dir1
20 | │ ├── file1.js
21 | │ └── file1.test.js
22 | ├── dir2
23 | │ ├── file2.js
24 | │ ├── file2.test.js
25 | │ ├── file3.js
26 | │ └── file3.test.js
27 | ```
28 | React comes with Jest preinstalled, however, more will be added later on the consideration of different testing libraries for improved testing.
29 |
30 |
--------------------------------------------------------------------------------
/db/sql/permission/permission.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS permission;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3 |
4 | CREATE TYPE permission.id_type AS ENUM ('USER', 'ROLE', 'TEAM');
5 | CREATE TYPE permission.type AS ENUM ('CREATE', 'DELETE', 'VIEW', 'EDIT');
6 | CREATE TYPE permission.category AS ENUM ('USER', 'BOOKING', 'PERMISSION', 'ROLE', 'TEAM', 'RESOURCE');
7 | CREATE TYPE permission.tenant AS ENUM ('ROLE', 'USER', 'TEAM', 'ASSOCIATION' , 'PERMISSION', 'BUILDING', 'ROOM', 'ROOMASSOCIATION', 'IDENTIFIER', 'MEETINGROOM', 'NA');
8 |
9 | CREATE TABLE IF NOT EXISTS permission.identifier (
10 | id uuid DEFAULT uuid_generate_v4(),
11 | permission_id uuid NOT NULL,
12 | permission_id_type permission.id_type NOT NULL,
13 | permission_type permission.type NOT NULL,
14 | permission_category permission.category NOT NULL,
15 | permission_tenant permission.tenant NOT NULL,
16 | permission_tenant_id uuid, -- If null then it means all
17 | date_added TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
18 |
19 | PRIMARY KEY (id)
20 | );
21 |
22 | CREATE UNIQUE INDEX permission_identifier_1 ON permission.identifier (permission_id, permission_id_type, permission_type, permission_category, permission_tenant, permission_tenant_id)
23 | WHERE permission_tenant_id is not null;
--------------------------------------------------------------------------------
/client/web/example/src/components/Role/RoleLeadOption.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback, useContext } from 'react';
2 | import { UserContext } from '../../App';
3 |
4 | const RoleLeadOption = ({id, roleLeadId}) =>
5 | {
6 | const [name, setName] = useState("error");
7 | const {userData} = useContext(UserContext);
8 | //POST request
9 | const getName = useCallback(() =>
10 | {
11 | fetch("http://localhost:8080/api/user/information",
12 | {
13 | method: "POST",
14 | mode: "cors",
15 | body: JSON.stringify({
16 | id: id
17 | }),
18 | headers:{
19 | 'Content-Type': 'application/json',
20 | 'Authorization': `bearer ${userData.token}` //Changed for frontend editing .token
21 | }
22 | }).then((res) => res.json()).then(data =>
23 | {
24 | setName(data[0].first_name + " " + data[0].last_name);
25 | });
26 | },[id]);
27 |
28 | //Using useEffect hook. This will set the default values of the form once the components are mounted
29 | useEffect(() =>
30 | {
31 | getName();
32 | }, [getName])
33 |
34 | return (
35 |
36 | )
37 | }
38 |
39 | export default RoleLeadOption
--------------------------------------------------------------------------------
/client/web/example/src/img/desk_grey.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db/sql/booking/booking.meeting_room.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION booking.meeting_room_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _booking_id uuid,
4 | _team_id uuid,
5 | _role_id uuid,
6 | _additional_attendees INT,
7 | _desks_attendees BOOLEAN,
8 | _desks_additional_attendees BOOLEAN
9 | )
10 | RETURNS uuid AS
11 | $$
12 | DECLARE
13 | __id uuid;
14 | BEGIN
15 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM booking.meeting_room WHERE id = _id)) THEN
16 | UPDATE booking.meeting_room
17 | SET id = _id,
18 | booking_id = _booking_id,
19 | team_id = _team_id,
20 | role_id = _role_id,
21 | additional_attendees = _additional_attendees,
22 | desks_attendees = _desks_attendees,
23 | desks_additional_attendees = _desks_additional_attendees
24 | WHERE id = _id
25 | RETURNING meeting_room.id INTO __id;
26 | ELSE
27 | INSERT INTO booking.meeting_room(id, booking_id, team_id, role_id, additional_attendees, desks_attendees, desks_additional_attendees)
28 | VALUES (COALESCE(_id, uuid_generate_v4()), _booking_id, _team_id, _role_id, _additional_attendees, _desks_attendees, _desks_additional_attendees)
29 | RETURNING meeting_room.id INTO __id;
30 | END IF;
31 | RETURN __id;
32 | END
33 | $$ LANGUAGE plpgsql;
34 |
--------------------------------------------------------------------------------
/client/web/example/src/img/desk_light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/web/example/src/components/User/user.module.css:
--------------------------------------------------------------------------------
1 | .editUserContainer
2 | {
3 | color: #374146;
4 | }
5 |
6 | .headerContainer
7 | {
8 | display: grid;
9 | grid-template-rows: 17vh 5vh;
10 |
11 | text-align: center;
12 | }
13 |
14 | .pictureContainer
15 | {
16 | width: 7vw;
17 | height: 7vw;
18 |
19 | margin-left: 6.5vw;
20 | margin-top: 2vh;
21 |
22 | border-radius: 10vh;
23 |
24 | background-color: #374146;
25 | }
26 |
27 | .picture
28 | {
29 | width: 100%;
30 | height: 100%;
31 |
32 | border-radius: 10vh;
33 | }
34 |
35 | .rolesContainer
36 | {
37 | overflow-x: hidden;
38 | overflow-y: scroll;
39 |
40 | width: 18vw;
41 |
42 | height: 20vh;
43 |
44 | margin-left: 1vw;
45 | margin-bottom: 2vh;
46 | padding-top: 1vh;
47 |
48 | border-top: 2px solid #374146;
49 |
50 | font-size: 1.5vw;
51 | }
52 |
53 | .roleLabel
54 | {
55 | margin-left: 0.5vw;
56 |
57 | font-size: 0.6vw;
58 | }
59 |
60 | .submit
61 | {
62 | width: 10vw;
63 | height: 4vh;
64 |
65 | margin-left: 5vw;
66 | margin-bottom: 2vh;
67 |
68 | border-radius: 1vh;
69 | border-style: none;
70 |
71 | font-size: 1vw;
72 |
73 | background-color: #09a2fb;
74 | color: white;
75 | }
76 |
77 | .submit:hover
78 | {
79 | cursor: pointer;
80 | background-color: #03639b;
81 | }
--------------------------------------------------------------------------------
/db/sql/resource/resource.identifier.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.identifier_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _room_id uuid,
4 | _name VARCHAR(256),
5 | _xcoord float,
6 | _ycoord float,
7 | _width float,
8 | _height float,
9 | _rotation float,
10 | _resource_type resource.type,
11 | _decorations JSON
12 | )
13 | RETURNS uuid AS
14 | $$
15 | DECLARE
16 | __id uuid;
17 | BEGIN
18 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM resource.identifier WHERE id = _id)) THEN
19 | UPDATE resource.identifier
20 | SET room_id = _room_id,
21 | name = _name,
22 | xcoord = _xcoord,
23 | ycoord = _ycoord,
24 | width = _width,
25 | height = _height,
26 | rotation = _rotation,
27 | resource_type = _resource_type
28 | WHERE id = _id
29 | RETURNING identifier.id INTO __id;
30 | ELSE
31 | INSERT INTO resource.identifier(id, room_id, name, xcoord, ycoord, width, height, rotation, resource_type, decorations)
32 | VALUES (COALESCE(_id, uuid_generate_v4()), _room_id, _name, _xcoord, _ycoord, _width, _height, _rotation, _resource_type, _decorations)
33 | RETURNING identifier.id INTO __id;
34 | END IF;
35 | RETURN __id;
36 | END
37 | $$ LANGUAGE plpgsql;
38 |
--------------------------------------------------------------------------------
/python/break.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import threading
3 | from tracemalloc import start
4 | from turtle import st
5 | from typing import Dict
6 | import requests
7 |
8 | def login_get_token() -> str:
9 | body: dict[str] = {
10 | "id": None,
11 | "secret": None,
12 | "active": None,
13 | "FailedAttempts": None,
14 | "LastAccessed": None,
15 | "Identifier": None
16 | }
17 | resp = requests.post("http://localhost:8100/api/user/login", json=body)
18 | return "bearer " + str(resp.json()["token"])
19 |
20 | def book(token: str, i) -> int:
21 | body: Dict[str] = {
22 | "user_id": "00000000-0000-0000-0000-000000000000",
23 | "resource_type": "DESK",
24 | "resource_preference_id": None,
25 | "start": f"2022-11-0{i+1}T12:12:00.000Z",
26 | "end": f"2022-11-0{i+1}T13:12:00.000Z",
27 | "automated": True,
28 | "booked": False
29 | }
30 | headers: Dict[str] = {
31 | "Authorization": token
32 | }
33 | resp = requests.post("http://localhost:8100/api/booking/create", json=body, headers=headers)
34 | print(i, "STATUS", resp)
35 | return resp.status_code
36 |
37 | token = login_get_token()
38 | print("logged in: ", token)
39 | for i in range(5):
40 | # book(token, i)
41 | x = threading.Thread(target=book, args=(token,2))
42 | x.start()
43 | x.join()
--------------------------------------------------------------------------------
/client/web/example/src/pages/BookingsMeetingRoom.js:
--------------------------------------------------------------------------------
1 | import Navbar from "../components/Navbar/Navbar.js"
2 | import { useContext, useEffect, useRef } from "react"
3 | import MeetingRoomBooking from "../components/BookingForm/MeetingRoomBooking"
4 | import ProfileBar from "../components/Navbar/ProfileBar.js";
5 | import NavbarAdmin from "../components/Navbar/NavbarAdmin.js";
6 | import { UserContext } from '../App';;
7 |
8 | const BookingsMeetingRoom = () =>
9 | {
10 | const deskRef = useRef(null);
11 | const mainRef = useRef(null);
12 | const {userData} = useContext(UserContext);
13 |
14 | useEffect(() =>
15 | {
16 | mainRef.current.style.overflowY = 'scroll';
17 | },[])
18 |
19 | const showNavbar = () =>
20 | {
21 | if(!userData.user_identifier.includes("admin"))
22 | {
23 | return ;
24 | }
25 | else
26 | {
27 | return ;
28 | }
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 | {showNavbar()}
36 |
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default BookingsMeetingRoom
--------------------------------------------------------------------------------
/go/api/endpoints/statistics.go:
--------------------------------------------------------------------------------
1 | package endpoints
2 |
3 | import (
4 | "api/data"
5 | "api/db"
6 | "fmt"
7 | "lib/logger"
8 | "lib/utils"
9 | "net/http"
10 |
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | //StatisticsHandlers registers the user
15 | func StatisticsHandlers(router *mux.Router) error {
16 | // router.HandleFunc("/resource_utilisation", ResourceUtilisation).Methods("POST")
17 | router.HandleFunc("/all", AllHandler).Methods("POST")
18 | return nil
19 | }
20 |
21 | func AllHandler(writer http.ResponseWriter, request *http.Request) {
22 | var statistics data.OverallStatics
23 | err := utils.UnmarshalJSON(writer, request, &statistics)
24 | if err != nil {
25 | fmt.Println(err)
26 | utils.BadRequest(writer, request, "invalid_request")
27 | return
28 | }
29 |
30 | // Create a database connection
31 | access, err := db.Open()
32 | if err != nil {
33 | utils.InternalServerError(writer, request, err)
34 | return
35 | }
36 | defer access.Close()
37 |
38 | da := data.NewStatisticsDA(access)
39 | all_statistics, err := da.GetAllStatistics()
40 | if err != nil {
41 | utils.InternalServerError(writer, request, err)
42 | return
43 | }
44 |
45 | // Commit transaction
46 | err = access.Commit()
47 | if err != nil {
48 | utils.InternalServerError(writer, request, err)
49 | return
50 | }
51 |
52 | logger.Access.Print("All statistics information requested\n")
53 | utils.JSONResponse(writer, request, all_statistics)
54 | }
55 |
--------------------------------------------------------------------------------
/.github/workflows/metatestPullRequest.yml:
--------------------------------------------------------------------------------
1 | name: 'Meta Test for Pull Request'
2 |
3 | on:
4 | pull_request:
5 | types: [ opened, reopened, labeled ]
6 |
7 | jobs:
8 | label-check:
9 | name: Checks if labels are used
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Has at least one type label
17 | uses: mheap/github-action-required-labels@v1
18 | with:
19 | mode: minimum
20 | count: 1
21 | labels: 'type:architecture, type:bug, type:documentation, type:feature, type:fix, type:refractor, type:test'
22 |
23 | - name: Has at least one priority label
24 | uses: mheap/github-action-required-labels@v1
25 | with:
26 | mode: minimum
27 | count: 1
28 | labels: 'priority:high, priority:low, priority:medium'
29 |
30 | - name: Has at least one scope label
31 | uses: mheap/github-action-required-labels@v1
32 | with:
33 | mode: minimum
34 | count: 1
35 | labels: 'scope:api, scope:cicd, scope:client, scope:db, scope:scheduler'
36 |
37 | milestone-check:
38 | name: Checks if milestone is used
39 | runs-on: ubuntu-latest
40 | steps:
41 | - name: Has milestone check
42 | if: github.event.pull_request.milestone == null
43 | run: |
44 | echo "A milestone is required"
45 | exit 1
--------------------------------------------------------------------------------
/go/scheduler/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
5 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
11 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
12 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
16 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
17 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18 |
--------------------------------------------------------------------------------
/client/web/example/src/img/desk_white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/web/example/src/img/desk_purple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db/sql/booking/booking.identifier.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION booking.identifier_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _user_id uuid,
4 | _resource_type resource.type,
5 | _resource_preference_id uuid,
6 | _resource_id uuid,
7 | _start TIMESTAMP WITHOUT TIME ZONE,
8 | _end TIMESTAMP WITHOUT TIME ZONE,
9 | _booked BOOLEAN DEFAULT NULL, -- Defaults to false and is not considered for creation
10 | _automated BOOLEAN DEFAULT NULL,
11 | _dependent uuid DEFAULT NULL
12 | )
13 | RETURNS uuid AS
14 | $$
15 | DECLARE
16 | __id uuid;
17 | BEGIN
18 | IF (_id IS NOT NULL AND EXISTS(SELECT 1 FROM booking.identifier WHERE id = _id)) THEN
19 | UPDATE booking.identifier
20 | SET user_id = _user_id,
21 | resource_type = _resource_type,
22 | resource_preference_id = _resource_preference_id,
23 | resource_id = _resource_id,
24 | start = _start,
25 | "end" = _end,
26 | booked = _booked,
27 | automated = _automated,
28 | dependent = _dependent
29 | WHERE id = _id
30 | RETURNING identifier.id INTO __id;
31 | ELSE
32 | INSERT INTO booking.identifier(id, user_id, resource_type, resource_preference_id, resource_id, start, "end", booked, automated, dependent)
33 | VALUES (COALESCE(_id, uuid_generate_v4()), _user_id, _resource_type, _resource_preference_id, _resource_id, _start, _end, _booked, COALESCE(_automated, false) ,_dependent)
34 | RETURNING identifier.id INTO __id;
35 | END IF;
36 | RETURN __id;
37 | END
38 | $$ LANGUAGE plpgsql;
39 |
--------------------------------------------------------------------------------
/go/lib/test_setup/test_setup.go:
--------------------------------------------------------------------------------
1 | package test_setup
2 |
3 | import (
4 | "api/db"
5 | "fmt"
6 | dbm "lib/dbmigrate"
7 | dtdb "lib/dockertest_db"
8 | tu "lib/testutils"
9 | "testing"
10 | )
11 |
12 | func SetupTest(t *testing.T) dtdb.TestDb {
13 | // SETUP =============
14 | config := dtdb.TestDbConfig{
15 | Verbose: true,
16 | HostPort: "5433",
17 | HostAdrr: "127.0.0.1",
18 | }
19 | testdb, err := dtdb.StartTestDb(config) // Start DB
20 | if err != nil {
21 | t.Error(tu.Scolourf(tu.RED, "Could not start testDb, err: %v", err))
22 | t.FailNow()
23 | }
24 | // Perform migration
25 | migrator := dbm.AutoMigrate{
26 | MigratePath: "../../../db/sql",
27 | PathPatterns: []string{
28 | `*.schema.*sql`, // schema files first
29 | `*.function.*sql`, // function files second
30 | // `*mock.sql`, // will use own mock data
31 | },
32 | }
33 | err = migrator.Migrate(testdb.Db)
34 | if err != nil {
35 | t.Error(tu.Scolourf(tu.RED, "Could not performed migration, err: %v", err))
36 | t.FailNow()
37 | }
38 | // update env vars for db connection
39 | t.Setenv("DATABASE_DSN", testdb.Dsn)
40 |
41 | dbMaxIdleEnv := "5"
42 | t.Setenv("DATABASE_MAX_IDLE_CONNECTIONS", dbMaxIdleEnv)
43 |
44 | dbMaxOpenEnv := "5"
45 | t.Setenv("DATABASE_MAX_OPEN_CONNECTIONS", dbMaxOpenEnv)
46 |
47 | err = db.RegisterAccess()
48 | if err != nil {
49 | t.Logf(tu.Scolour(tu.PURPLE, "Error while connecting to DB: %v, skipping test"), err)
50 | t.FailNow()
51 | } else {
52 | fmt.Println(tu.Scolour(tu.GREEN, "DB connected"))
53 | }
54 |
55 | return testdb
56 | // ==================
57 | }
58 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Logout/__test__/LogoutButton.test.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter } from 'react-router-dom'
2 | import React, { useContext, useState } from 'react'
3 | import LogoutButton from '../LogoutButton.js'
4 | import {act, render, fireEvent, cleanup, screen} from '@testing-library/react';
5 | import { UserContext } from '../../../App';
6 |
7 | import * as router from 'react-router'
8 |
9 | const navigate = jest.fn();
10 |
11 | const MockLogoutButton = () => {
12 | const [userData, setUserData] = useState(null);
13 |
14 | return(
15 |
16 |
17 |
18 |
19 |
20 | )
21 | };
22 |
23 | beforeEach(() => {
24 | jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate);
25 | jest.spyOn(window.localStorage.__proto__, 'removeItem');
26 | });
27 |
28 | afterEach(cleanup);
29 |
30 | describe('When clicking on Logout button', () => {
31 | it('should navigate to /login', () => {
32 | render( );
33 |
34 | expect(screen.getByTestId('button-user-profile')).toBeInTheDocument();
35 | fireEvent.click(screen.getByTestId('button-user-profile'));
36 | expect(navigate).toHaveBeenCalledWith('/login');
37 | });
38 |
39 | it('should clear local storage', () => {
40 | render( );
41 |
42 | expect(screen.getByTestId('button-user-profile')).toBeInTheDocument();
43 | fireEvent.click(screen.getByTestId('button-user-profile'));
44 | expect(localStorage.removeItem).toHaveBeenCalledWith('auth_data');
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS resource;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3 |
4 | CREATE TYPE resource.type AS ENUM ('PARKING', 'DESK', 'MEETINGROOM', 'WALL');
5 |
6 | CREATE TABLE IF NOT EXISTS resource.building (
7 | id uuid DEFAULT uuid_generate_v4(),
8 | name VARCHAR(256),
9 | location VARCHAR(256), -- TODO [KP]: CHANGE TO WHAT WE USE
10 | dimension VARCHAR(256) NOT NULL DEFAULT('5x5'),
11 |
12 | PRIMARY KEY (id)
13 | );
14 |
15 | CREATE TABLE IF NOT EXISTS resource.room (
16 | id uuid DEFAULT uuid_generate_v4(),
17 | building_id uuid REFERENCES resource.building(id) ON DELETE CASCADE,
18 | name VARCHAR(256),
19 | xcoord float,
20 | ycoord float,
21 | zcoord float,
22 | dimension VARCHAR(256) NOT NULL DEFAULT('5x5'),
23 |
24 | PRIMARY KEY (id)
25 | );
26 |
27 | CREATE TABLE IF NOT EXISTS resource.room_association (
28 | room_id uuid NOT NULL REFERENCES resource.room(id) ON DELETE CASCADE,
29 | room_id_association uuid NOT NULL REFERENCES resource.room(id) ON DELETE CASCADE,
30 |
31 | PRIMARY KEY (room_id, room_id_association)
32 | );
33 |
34 | CREATE TABLE IF NOT EXISTS resource.identifier (
35 | id uuid DEFAULT uuid_generate_v4(),
36 | room_id uuid REFERENCES resource.room(id) ON DELETE CASCADE,
37 | name VARCHAR(256),
38 | xcoord float,
39 | ycoord float,
40 | width float,
41 | height float,
42 | rotation float,
43 | resource_type resource.type NOT NULL,
44 | date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
45 | decorations JSON NOT NULL,
46 |
47 | PRIMARY KEY (id)
48 | );
--------------------------------------------------------------------------------
/client/web/example/src/components/Calendar/Borders.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './calendar.module.css';
3 |
4 | const Borders = () =>
5 | {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default Borders
--------------------------------------------------------------------------------
/proxy/Arche-Server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEAsWKRsIPbciAwdk/XiO8PzDIMomjcvTAfRN7h+wgVsV2WnbEz
3 | yrTn4MDytDDFBseAvgSuKGdfn3wSpJFQVnQ69dS3g8KF8bS2ZaSuE6BDQCxPFqk+
4 | MfNvXebdKdt4IstsX3ev+O/NyvrJ1Tz17dJJaBagkh2S463pWBTje07z66keq0eY
5 | oirWYm6KJtDxnD7yrgukvufTAov2p7PGB9k1ci7nGuw2+BiLtbsp8y9j7rmYJEVb
6 | QFbq6ELv1WU57B1rrleZo5OeV7MVhY/l9l8dnBpO3rEuJGd+pxwxbJ0ghEKM/QOq
7 | JovKMF85p+1zlv4yE7O77b+gpo4UIQxN47PnuQIDAQABAoIBAQCjecHmuT5OiVfg
8 | UWjDq/4VI7L9IkL8oBLRN89vuF21nhimr56Zm+tu+KM0aRXLT6VYeZu/ZLV9l6ze
9 | uYUeh/OmhymxZl5nCTJnrkM3lyUjSTKCIetNwZnLIm6Emty2xF7WBMnKU8aG4GWN
10 | /aZwTLKWGPRWdKbg6W/5Wxbilzq43RRwQ8yZMCKYIIVFgrR9assCbjV1meZ2Nzq1
11 | OYQU0WqKlBsvOkiJHmL1ZHs815oty/dVXk9eaEHYJhwcVIrv/fuFI/kZDwiZBgHe
12 | gWP0HE1c48DN7lWI3rrbuJpkmEQVjvurz2+jsynz6843GEV5uA7EZXHYZzqR9AXL
13 | 6rJ/Ulv5AoGBAN2QOHdLweeuR3LuQTzuPvUA333Osu9/z6W7dWLlkj4b2i9fLcCS
14 | Q2lZxglgUNx+/wfFatr3eRAVvvK5ipuYry7f8i4B5IfG4fgk+cbucvBAnbqs/uY+
15 | /dJd/mA7lW6Jf9l+jZugx3yyBlwonA2eMJ8qzevk73iLD8k8ltBbJsVbAoGBAMz0
16 | icPJ3WrOsJVX14pt5i9QLGpVArIOBjCsi7oOgr/YnN4JJpIe1Tolms89INdRl5DQ
17 | bDpUG90cAyK6cwXc6rV5F97zH3ZJ9EYkOV8NWMm85phF4CTsSV4vJRk3ZMEExqVx
18 | rnUMWrahZHpwRwudT8e0Bm52sFIuxranUesvRE97AoGBAIVIPK+G4x57uH9q09PT
19 | fgvkeo0zynBIcqFAI11avJ6gIqBcoSk201aMqpT5W6tIuiHyoFUYibsjWbqp4re3
20 | IasRDJ6ghNkoysZ7d8YzqvDb7gZeCvh27DmgUWWSclIMZNxxRL5Z+acJcMn/o+CN
21 | SLJ3DWjaTzQ0c0qbKB4utyQBAoGAb7IFMlJc/MxPp9QLf0GNZNOIoRpbMbY64WPn
22 | cipeTzc5pjfOcWukQOAlkjN6Jl4s/5rWoKMd3E5b1x1NejB/KQ8pVzOgPdLn8SHk
23 | skyPysHL2cia8wRip5WB3lA19IspxxhBP0DQXx9tptxxm37L78XIi4LbbK5ZVUmy
24 | dz13OOkCgYBFM0HQJ2EzfsRhzDkFw+C1RWwEA1gS8o/b2jKtRNSKtp2wnGt4d9C6
25 | YokeZ2Ke1cBX5ez/0Jvzks67nQd4buBd4RYuYgWKAYkWHt1ufmHDr9Da5PAmVVDL
26 | 6fVmCbJJe+O1ZojAYcaPyQlGQkDwBYQhofhFk71we97899I3TmiZDw==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Navbar/ProfileBar.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useRef, useState } from 'react'
2 | import { useNavigate } from "react-router-dom"
3 | import { FaCalendar, FaTicketAlt, FaMap, FaChartPie, FaDoorOpen } from 'react-icons/fa'
4 | import { CgProfile } from 'react-icons/cg'
5 | import { UserContext } from '../../App';
6 |
7 | const ProfileBar = (props, ref) =>
8 | {
9 | const navigate = useNavigate();
10 | const profileRef = useRef(null);
11 |
12 | const {userData,setUserData} = useContext(UserContext);
13 | const [currLocation, setCurrLocation] = useState("");
14 |
15 | const NavigateProfile = () =>
16 | {
17 | navigate("/profile");
18 | }
19 |
20 | const Logout = () =>
21 | {
22 | setUserData(null);
23 | localStorage.removeItem("auth_data");
24 | navigate("/login");
25 | }
26 |
27 | useEffect(() =>
28 | {
29 | if(currLocation === "/profile")
30 | {
31 | profileRef.current.style.color = "#09a2fb";
32 | }
33 |
34 | },[currLocation])
35 |
36 | useEffect(() =>
37 | {
38 | setCurrLocation(window.location.pathname);
39 | },[])
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Logout
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default React.forwardRef(ProfileBar)
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.association.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_association_find(
2 | _room_id uuid DEFAULT NULL,
3 | _room_id_association uuid DEFAULT NULL,
4 | _permissions JSONB DEFAULT NULL
5 | )
6 | RETURNS TABLE (
7 | room_id uuid,
8 | room_id_association uuid
9 | ) AS
10 | $$
11 | BEGIN
12 | CREATE TEMP TABLE _permissions_table (
13 | permission_type permission.type,
14 | permission_category permission.category,
15 | permission_tenant permission.tenant,
16 | permission_tenant_id uuid
17 | );
18 |
19 | INSERT INTO _permissions_table (
20 | SELECT
21 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
22 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
23 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
24 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
25 | );
26 |
27 | RETURN QUERY
28 | WITH permitted_associations AS (
29 | SELECT permission_tenant_id FROM _permissions_table
30 | WHERE permission_type = 'VIEW'::permission.type
31 | AND permission_category = 'RESOURCE'::permission.category
32 | AND permission_tenant = 'ROOMASSOCIATION'::permission.tenant
33 | )
34 | SELECT i.room_id, i.room_id_association
35 | FROM resource.room_association as i
36 | WHERE (EXISTS(SELECT 1 FROM permitted_associations WHERE permission_tenant_id is null) OR i.room_id = ANY(SELECT * FROM permitted_associations))
37 | AND (_room_id IS NULL OR i.room_id = _room_id)
38 | AND (_room_id_association IS NULL OR i.room_id_association = _room_id_association);
39 |
40 | DROP TABLE _permissions_table;
41 | END
42 | $$ LANGUAGE plpgsql;
43 |
--------------------------------------------------------------------------------
/client/web/example/src/pages/Admin.js:
--------------------------------------------------------------------------------
1 | import ProfileBar from '../components/Navbar/ProfileBar.js';
2 | import Navbar from '../components/Navbar/Navbar.js';
3 | import NavbarAdmin from '../components/Navbar/NavbarAdmin.js';
4 | import { useContext } from 'react';
5 | import { UserContext } from '../App.js';
6 | import Kanban from '../components/Kanban/Kanban.js';
7 |
8 | const Admin = () =>
9 | {
10 | const {userData} = useContext(UserContext);
11 |
12 | const showNavbar = () =>
13 | {
14 | if(userData !== null && !userData.user_identifier.includes("admin"))
15 | {
16 | return ;
17 | }
18 | else
19 | {
20 | return ;
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | {showNavbar()}
29 |
30 |
31 |
32 |
33 |
34 | {/*
39 |
*/}
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default Admin
--------------------------------------------------------------------------------
/db/sql/initdb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | USER="admin"
5 | DBNAME="arche"
6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
7 |
8 | echo "Using database $DBNAME"
9 |
10 | CONFIG=" dbname=$DBNAME user=$USER"
11 |
12 | ######### resource
13 | for file in ${DIR}/resource/*.schema.*sql
14 | do
15 | psql "$CONFIG" -f "$file"
16 | done
17 |
18 | for file in ${DIR}/resource/*.function.*sql
19 | do
20 | psql "$CONFIG" -f "$file"
21 | done
22 |
23 | ######### user
24 | for file in ${DIR}/user/*.schema.*sql
25 | do
26 | psql "$CONFIG" -f "$file"
27 | done
28 |
29 | for file in ${DIR}/user/*.function.*sql
30 | do
31 | psql "$CONFIG" -f "$file"
32 | done
33 |
34 | ######### team
35 | for file in ${DIR}/team/*.schema.*sql
36 | do
37 | psql "$CONFIG" -f "$file"
38 | done
39 |
40 | for file in ${DIR}/team/*.function.*sql
41 | do
42 | psql "$CONFIG" -f "$file"
43 | done
44 |
45 | ######### role
46 | for file in ${DIR}/role/*.schema.*sql
47 | do
48 | psql "$CONFIG" -f "$file"
49 | done
50 |
51 | for file in ${DIR}/role/*.function.*sql
52 | do
53 | psql "$CONFIG" -f "$file"
54 | done
55 |
56 | ######### permission
57 | for file in ${DIR}/permission/*.schema.*sql
58 | do
59 | psql "$CONFIG" -f "$file"
60 | done
61 |
62 | for file in ${DIR}/permission/*.function.*sql
63 | do
64 | psql "$CONFIG" -f "$file"
65 | done
66 |
67 | ######### booking
68 | for file in ${DIR}/booking/*.schema.*sql
69 | do
70 | psql "$CONFIG" -f "$file"
71 | done
72 |
73 | for file in ${DIR}/booking/*.function.*sql
74 | do
75 | psql "$CONFIG" -f "$file"
76 | done
77 |
78 | ######### mock
79 | for file in ${DIR}/mock/*.sql
80 | do
81 | psql "$CONFIG" -f "$file"
82 | done
83 |
84 | ######### statistics
85 | for file in ${DIR}/statistics/*.schema.*sql
86 | do
87 | psql "$CONFIG" -f "$file"
88 | done
89 |
90 | for file in ${DIR}/statistics/*.function.*sql
91 | do
92 | psql "$CONFIG" -f "$file"
93 | done
94 |
--------------------------------------------------------------------------------
/go/lib/testutils/testutils.go:
--------------------------------------------------------------------------------
1 | package testutils
2 |
3 | import (
4 | // "api/db"
5 | "fmt"
6 | "lib/collectionutils"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | type Colour string
13 |
14 | // Used for test output formatting, if colours should be added
15 | const (
16 | RESET Colour = "\033[0m"
17 | RED Colour = "\033[31m"
18 | GREEN Colour = "\033[32m"
19 | YELLOW Colour = "\033[33m"
20 | BLUE Colour = "\033[34m"
21 | PURPLE Colour = "\033[35m"
22 | CYAN Colour = "\033[36m"
23 | GRAY Colour = "\033[37m"
24 | WHITE Colour = "\033[97m"
25 | )
26 |
27 | // Adds the colour to the string
28 | func Scolour(colour Colour, msg string) string {
29 | return string(colour) + msg + string(RESET)
30 | }
31 |
32 | // Formats a string, and adds ansi escape sequences to colour the string
33 | func Scolourf(colour Colour, format string, a ...interface{}) string {
34 | return fmt.Sprintf(Scolour(colour, format), a...)
35 | }
36 |
37 | // Returns a pointer to the value passed in, mainly used for passing in literals
38 | func Ptr[T any](obj T) *T { return &obj }
39 |
40 | // MapsWithcSlicesMatchLoosely function compares two maps that map two arrays, it checks that the keys of the
41 | // maps are identical, and that the arrays mapped two match loosely, meaning the elements of the arrays match, but not necessarily the order
42 | func MapsWithcSlicesMatchLoosely[K comparable, V any](t *testing.T, map1 map[K][]V, map2 map[K][]V, msgAndArgs ...interface{}) {
43 | if len(map1) != len(map2) {
44 | assert.Fail(t, "Len of maps should be same", msgAndArgs...)
45 | }
46 |
47 | for key, value := range map1 {
48 | if !collectionutils.MapHasKey(map2, key) {
49 | assert.Fail(t, "Maps should have matching keys", msgAndArgs...)
50 | return
51 | }
52 | assert.ElementsMatch(t, value, map2[key], msgAndArgs...)
53 | if t.Failed() {
54 | return
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/db/sql/user/user.identifier.function.store.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".identifier_store(
2 | _id uuid, -- NULLABLE, If supplied try update else insert
3 | _identifier VARCHAR(256),
4 | _first_name VARCHAR(256),
5 | _last_name VARCHAR(256),
6 | _email VARCHAR(256),
7 | _picture TEXT,
8 | _work_from_home BOOLEAN,
9 | _parking parking.type,
10 | _office_days INTEGER,
11 | _preferred_start_time TIME WITHOUT TIME ZONE,
12 | _preferred_end_time TIME WITHOUT TIME ZONE,
13 | _preferred_desk uuid DEFAULT NULL,
14 | _building_id uuid DEFAULT NULL
15 | )
16 | RETURNS uuid AS
17 | $$
18 | DECLARE
19 | __id uuid;
20 | BEGIN
21 | IF EXISTS(SELECT 1 FROM "user".identifier WHERE identifier = _identifier AND id = _id) THEN
22 | UPDATE "user".identifier
23 | SET first_name = _first_name,
24 | last_name = _last_name,
25 | email = _email,
26 | picture = _picture,
27 | work_from_home = _work_from_home,
28 | parking = _parking,
29 | office_days = _office_days,
30 | preferred_start_time = _preferred_start_time,
31 | preferred_end_time = _preferred_end_time,
32 | preferred_desk = _preferred_desk,
33 | building_id = _building_id
34 | WHERE identifier = _identifier
35 | RETURNING identifier.id INTO __id;
36 | ELSE
37 | INSERT INTO "user".identifier (id, identifier, first_name, last_name, email, picture, work_from_home, parking, office_days, preferred_start_time, preferred_end_time, preferred_desk, building_id)
38 | VALUES (COALESCE(_id, uuid_generate_v4()), _identifier, _first_name, _last_name, _email, _picture, _work_from_home, _parking, _office_days, _preferred_start_time, _preferred_end_time, _preferred_desk, _building_id)
39 | RETURNING identifier.id INTO __id;
40 | END IF;
41 | RETURN __id;
42 | END
43 | $$ LANGUAGE plpgsql;
44 |
--------------------------------------------------------------------------------
/db/sql/team/team.association.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.association_find(
2 | _team_id uuid DEFAULT NULL,
3 | _team_id_association uuid DEFAULT NULL,
4 | _permissions JSONB DEFAULT NULL -- Must only contain team_id's
5 | )
6 | RETURNS TABLE (
7 | team_id uuid,
8 | team_id_association uuid
9 | ) AS
10 | $$
11 | BEGIN
12 | CREATE TEMP TABLE _permissions_table (
13 | permission_type permission.type,
14 | permission_category permission.category,
15 | permission_tenant permission.tenant,
16 | permission_tenant_id uuid
17 | );
18 |
19 | INSERT INTO _permissions_table (
20 | SELECT
21 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
22 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
23 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
24 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
25 | );
26 |
27 | RETURN QUERY
28 | WITH permitted_teams AS (
29 | SELECT permission_tenant_id FROM _permissions_table
30 | WHERE permission_type = 'VIEW'::permission.type
31 | AND permission_category = 'TEAM'::permission.category
32 | AND permission_tenant = 'ASSOCIATION'::permission.tenant
33 | )
34 | SELECT i.team_id, i.team_id_association
35 | FROM team.association as i
36 | WHERE (EXISTS(SELECT 1 FROM permitted_teams WHERE permission_tenant_id is null) OR i.team_id = ANY(SELECT * FROM permitted_teams))
37 | AND (EXISTS(SELECT 1 FROM permitted_teams WHERE permission_tenant_id is null) OR i.team_id_association = ANY(SELECT * FROM permitted_teams))
38 | AND (_team_id IS NULL OR i.team_id = _team_id)
39 | AND (_team_id_association IS NULL OR i.team_id_association = _team_id_association);
40 |
41 | DROP TABLE _permissions_table;
42 | END
43 | $$ LANGUAGE plpgsql;
44 |
--------------------------------------------------------------------------------
/python/mockdatapool.json:
--------------------------------------------------------------------------------
1 | {
2 | "first_names": [
3 | "Hrothgar",
4 | "Golyat",
5 | "Parth",
6 | "Arlie",
7 | "Maksym",
8 | "Zsuzsa",
9 | "Azhar",
10 | "Tutgual",
11 | "Gogi",
12 | "Blacksneer",
13 | "Stamatios",
14 | "Nuria",
15 | "Scarlett",
16 | "Antonija",
17 | "Wilfrið",
18 | "Natalie",
19 | "Kichirou",
20 | "Christine",
21 | "Trine",
22 | "Tryggvi",
23 | "Charlotte ",
24 | "Jannick ",
25 | "Erik ",
26 | "Lachlann ",
27 | "Fife ",
28 | "Mòrag ",
29 | "Rory ",
30 | "Erna ",
31 | "Inga ",
32 | "Auðrhildr ",
33 | "Kjersti ",
34 | "Regina ",
35 | "Guðmundr ",
36 | "an ",
37 | "Kenji ",
38 | "Ursula ",
39 | "Lise ",
40 | "Nina ",
41 | "Erling ",
42 | "Naoki ",
43 | "Sander ",
44 | "Hallstein ",
45 | "Isabella ",
46 | "Satomi ",
47 | "Oliver "
48 | ],
49 | "last_names": [
50 | "Minami",
51 | "Kurata",
52 | "Patton",
53 | "Balfour",
54 | "Riber",
55 | "Haraguchi",
56 | "Aoki",
57 | "Cockburn",
58 | "Faulkner",
59 | "Katō",
60 | "Shiratori",
61 | "MacIver",
62 | "Abramsen",
63 | "Synnøve Mac Aba",
64 | "Karstensen",
65 | "Karstensen",
66 | "Shinohara",
67 | "Saitō",
68 | "Lindsay",
69 | "Leslie",
70 | "McTaggart",
71 | "Finley",
72 | "Watson",
73 | "McClellan",
74 | "Ainsley"
75 | ],
76 | "email_domains": [
77 | "gmail.com",
78 | "oultook.com",
79 | "tuks.co.za",
80 | "email.com"
81 | ],
82 | "passwords": [
83 | "badp@ssword"
84 | ]
85 | }
--------------------------------------------------------------------------------
/go/scheduler/ga/selection.go:
--------------------------------------------------------------------------------
1 | package ga
2 |
3 | type Comparator func(a, b float64) bool
4 |
5 | ///////////////////////////////////////////////////
6 | // WEEKLY
7 |
8 | // Returns a >= b
9 | func GTorEq(a, b float64) bool {
10 | return a >= b
11 | }
12 |
13 | // Returns a < b
14 | func LT(a, b float64) bool {
15 | return a < b
16 | }
17 |
18 | func WeeklyStubSelection(domain *Domain, individuals Individuals, count int) Individuals {
19 | return individuals[:count]
20 | }
21 |
22 | func WeeklyTournamentSelectionFitness(domain *Domain, individuals Individuals, count int) Individuals {
23 | return WeeklyTournamentSelection(domain, individuals, count, GTorEq)
24 | }
25 |
26 | func WeeklyTournamentSelectionCost(domain *Domain, individuals Individuals, count int) Individuals {
27 | return WeeklyTournamentSelection(domain, individuals, count, LT)
28 | }
29 |
30 | func WeeklyTournamentSelection(domain *Domain, individuals Individuals, count int, comparator Comparator) Individuals {
31 | var results Individuals
32 | for i := 0; i < count; i++ {
33 | results = append(results, weeklyTournamentSelection(domain, individuals, comparator))
34 | }
35 | return results
36 | }
37 |
38 | func weeklyTournamentSelection(domain *Domain, individuals Individuals, comparator Comparator) *Individual {
39 | var tournament Individuals
40 |
41 | for i := 0; i <= domain.Config.TournamentSize; i++ {
42 | tournament = append(tournament, individuals.GetRandomIndividual().Clone())
43 | }
44 |
45 | var winner *Individual = tournament[0]
46 |
47 | for _, competitor := range tournament {
48 | // if competitor.Fitness >= winner.Fitness {
49 | if comparator(competitor.Fitness, winner.Fitness) {
50 | // if winner.Fitness <= competitor.Fitness {
51 | winner = competitor
52 | }
53 | }
54 |
55 | return winner
56 | }
57 |
58 | // SELECTION FUNCTION: FITNESS PROPORTIONATE SELECTION
59 |
60 | ///////////////////////////////////////////////////
61 | // DAILY
62 |
--------------------------------------------------------------------------------
/client/web/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emailjs/browser": "^3.6.2",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^13.1.1",
9 | "@testing-library/user-event": "^13.5.0",
10 | "apexcharts": "^3.35.5",
11 | "bootstrap": "^5.1.3",
12 | "firebase": "^9.10.0",
13 | "konva": "^8.3.10",
14 | "react": "^18.0.0",
15 | "react-apexcharts": "^1.4.0",
16 | "react-beautiful-dnd": "^13.1.1",
17 | "react-bootstrap": "^2.3.1",
18 | "react-dom": "^18.0.0",
19 | "react-donut-chart": "^1.3.1",
20 | "react-gauge-chart": "^0.4.0",
21 | "react-icons": "^4.3.1",
22 | "react-konva": "^18.2.1",
23 | "react-password-strength-bar": "^0.4.1",
24 | "react-router-dom": "^6.3.0",
25 | "react-scripts": "^5.0.1",
26 | "react-svg-gauge": "^1.0.10",
27 | "styled-components": "^5.3.5",
28 | "use-image": "^1.0.12",
29 | "uuid": "^9.0.0",
30 | "web-vitals": "^2.1.4"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "CI=false && react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject"
37 | },
38 | "eslintConfig": {
39 | "extends": [
40 | "react-app",
41 | "react-app/jest"
42 | ]
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | },
56 | "devDependencies": {
57 | "@types/react-beautiful-dnd": "^13.1.2",
58 | "jest-fetch-mock": "^3.0.3",
59 | "jest-watch-typeahead": "^0.6.5"
60 | },
61 | "jest": {
62 | "transformIgnorePatterns": [
63 | "node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)"
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Navbar/__test__/ProfileBar.test.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter } from 'react-router-dom'
2 | import React, { useContext, useState } from 'react'
3 | import ProfileBar from '../ProfileBar.js'
4 | import {act, render, fireEvent, cleanup, screen} from '@testing-library/react';
5 | import { UserContext } from '../../../App';
6 |
7 | import * as router from 'react-router'
8 |
9 | const navigate = jest.fn();
10 |
11 | const MockProfileBar = () => {
12 | const [userData, setUserData] = useState(null);
13 |
14 | return(
15 |
16 |
17 |
18 |
19 |
20 | )
21 | };
22 |
23 | beforeEach(() => {
24 | jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate);
25 | jest.spyOn(window.localStorage.__proto__, 'removeItem');
26 | });
27 |
28 | afterEach(cleanup);
29 |
30 | describe('When clicking on Profile picture', () => {
31 | it('should navigate to /profile', () => {
32 | render( );
33 |
34 | expect(screen.getByTestId('profilepic-container')).toBeInTheDocument();
35 | fireEvent.click(screen.getByTestId('profilepic-container'));
36 | expect(navigate).toHaveBeenCalledWith('/profile');
37 | });
38 | });
39 |
40 | describe('When clicking on Logout', () => {
41 | it('should navigate to /login', () => {
42 | render( );
43 |
44 | expect(screen.getByText(/Logout/i)).toBeInTheDocument();
45 | fireEvent.click(screen.getByText(/Logout/i));
46 | expect(navigate).toHaveBeenCalledWith('/login');
47 | });
48 |
49 | it('should clear local storage', () => {
50 | render( );
51 |
52 | expect(screen.getByText(/Logout/i)).toBeInTheDocument();
53 | fireEvent.click(screen.getByText(/Logout/i));
54 | expect(localStorage.removeItem).toHaveBeenCalledWith('auth_data');
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/db/sql/user/user.schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS "user";
2 | CREATE SCHEMA IF NOT EXISTS parking;
3 | CREATE EXTENSION IF NOT EXISTS pgcrypto;
4 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
5 |
6 | CREATE TYPE parking.type AS ENUM ('NONE', 'STANDARD', 'DISABLED');
7 |
8 | CREATE TABLE IF NOT EXISTS "user".identifier (
9 | id uuid DEFAULT uuid_generate_v4(),
10 | identifier VARCHAR(256) UNIQUE NOT NULL,
11 | first_name VARCHAR(256) CHECK(first_name <> ''),
12 | last_name VARCHAR(256) CHECK(last_name <> ''),
13 | email VARCHAR(256) CHECK(email <> ''),
14 | picture TEXT CHECK(picture <> ''),
15 | date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
16 | work_from_home BOOLEAN NOT NULL DEFAULT false,
17 | parking parking.type NOT NULL DEFAULT 'STANDARD',
18 | office_days INTEGER NOT NULL DEFAULT 0,
19 | preferred_start_time TIME WITHOUT TIME ZONE DEFAULT NULL,
20 | preferred_end_time TIME WITHOUT TIME ZONE DEFAULT NULL,
21 | preferred_desk uuid REFERENCES resource.identifier(id) ON DELETE CASCADE DEFAULT NULL,
22 | building_id uuid REFERENCES resource.building(id) ON DELETE CASCADE DEFAULT NULL,
23 |
24 | PRIMARY KEY (id)
25 | );
26 |
27 | CREATE TYPE "user".credential_type AS ENUM ('federated', 'local');
28 |
29 | CREATE TABLE IF NOT EXISTS "user".credential (
30 | id VARCHAR(256),
31 | secret VARCHAR(256),
32 | identifier VARCHAR(256) NOT NULL REFERENCES "user".identifier(identifier) ON DELETE CASCADE,
33 | "type" "user".credential_type generated always as (
34 | CASE
35 | WHEN secret IS NULL AND id NOT ILIKE 'local.%' THEN 'federated'::"user".credential_type
36 | ELSE 'local'::"user".credential_type
37 | END
38 | ) stored,
39 | active BOOLEAN NOT NULL,
40 | failed_attempts INT NOT NULL DEFAULT(0),
41 | last_accessed TIMESTAMP WITHOUT TIME ZONE DEFAULT(now() AT TIME ZONE 'uct'),
42 |
43 | PRIMARY KEY (id)
44 | );
--------------------------------------------------------------------------------
/go/api/scheduler/data_reciever.go:
--------------------------------------------------------------------------------
1 | package scheduler
2 |
3 | import (
4 | "api/data"
5 | "api/db"
6 | )
7 |
8 | type CandidateBookings []data.Bookings
9 |
10 | // makeBookings stores the created bookings in the database
11 | func makeBookings(candidates CandidateBookings, schedulerData *SchedulerData) error {
12 | choose := 0 // Right now just choose any set of bookings, no heuristic yet
13 | if len(candidates) == 0 {
14 | return nil // No bookings to be made
15 | }
16 | access, err := db.Open()
17 | if err != nil {
18 | return err
19 | }
20 | defer access.Close()
21 |
22 | bookings := data.BatchBooking{
23 | UserId: nil,
24 | Bookings: candidates[choose],
25 | }
26 |
27 | da := data.NewBatchBookingDA(access)
28 | err = da.StoreIdentifiers(&bookings)
29 | if err != nil {
30 | return err
31 | }
32 | err = access.Commit()
33 | if err != nil {
34 | return err
35 | }
36 | // go func() { // Make bookings
37 | // accessGoogle, err := db.Open()
38 | // if err != nil {
39 | // logger.Error.Print("Could not create access: ", err)
40 | // return
41 | // }
42 | // defer accessGoogle.Close()
43 | // for _, booking := range candidates[choose] {
44 | // if booking.ResourceId != nil {
45 | // du := data.NewUserDA(accessGoogle)
46 | // users, err := du.FindIdentifier(&data.User{Id: booking.UserId})
47 | // if err != nil {
48 | // logger.Error.Printf("User not found when creating calendar booking. User ID: %v, err: %v\n", *booking.UserId, err)
49 | // continue
50 | // }
51 | // user := users.FindHead()
52 | // err = google_api.CreateUpdateBooking(user, booking)
53 | // if err != nil {
54 | // logger.Error.Println("User not found when creating calendar booking")
55 | // continue
56 | // }
57 | // err = du.Commit()
58 | // if err != nil {
59 | // logger.Error.Printf("Could not commit %v", err)
60 | // }
61 | // }
62 | // }
63 | // }()
64 | return nil
65 | }
66 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.building.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.building_find(
2 | _id uuid DEFAULT NULL,
3 | _name VARCHAR(256) DEFAULT NULL,
4 | _location VARCHAR(256) DEFAULT NULL,
5 | _dimension VARCHAR(256) DEFAULT NULL,
6 | _permissions JSONB DEFAULT NULL
7 | )
8 | RETURNS TABLE (
9 | id uuid,
10 | name VARCHAR(256),
11 | location VARCHAR(256),
12 | dimension VARCHAR(256)
13 | ) AS
14 | $$
15 | BEGIN
16 | CREATE TEMP TABLE _permissions_table (
17 | permission_type permission.type,
18 | permission_category permission.category,
19 | permission_tenant permission.tenant,
20 | permission_tenant_id uuid
21 | );
22 |
23 | INSERT INTO _permissions_table (
24 | SELECT
25 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
26 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
27 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
28 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
29 | );
30 |
31 | RETURN QUERY
32 | WITH permitted_buildings AS (
33 | SELECT permission_tenant_id FROM _permissions_table
34 | WHERE permission_type = 'VIEW'::permission.type
35 | AND permission_category = 'RESOURCE'::permission.category
36 | AND permission_tenant = 'BUILDING'::permission.tenant
37 | )
38 | SELECT i.id, i.name, i.location, i.dimension
39 | FROM resource.building as i
40 | WHERE (EXISTS(SELECT 1 FROM permitted_buildings WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_buildings))
41 | AND (_id IS NULL OR i.id = _id)
42 | AND (_name IS NULL OR i.name = _name)
43 | AND (_location IS NULL OR i.location = _location)
44 | AND (_dimension IS NULL OR i.dimension = _dimension);
45 |
46 | DROP TABLE _permissions_table;
47 | END
48 | $$ LANGUAGE plpgsql;
49 |
--------------------------------------------------------------------------------
/client/web/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Deskflow
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/go/scheduler/data/utils_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestTimeInIntervalInclusive(t *testing.T) {
9 | type args struct {
10 | check time.Time
11 | start time.Time
12 | end time.Time
13 | }
14 | tests := []struct {
15 | name string
16 | args args
17 | want bool
18 | }{
19 | {
20 | name: "Test 1",
21 | args: args{
22 | check: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
23 | start: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
24 | end: time.Date(2022, 9, 27, 11, 40, 00, 00, time.UTC),
25 | },
26 | want: true,
27 | },
28 | {
29 | name: "Test 2",
30 | args: args{
31 | check: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
32 | start: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
33 | end: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
34 | },
35 | want: true,
36 | },
37 | {
38 | name: "Test 3",
39 | args: args{
40 | check: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
41 | start: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
42 | end: time.Date(2022, 9, 27, 11, 40, 00, 00, time.UTC),
43 | },
44 | want: true,
45 | },
46 | {
47 | name: "Test 4",
48 | args: args{
49 | check: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
50 | start: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
51 | end: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
52 | },
53 | want: true,
54 | },
55 | {
56 | name: "Test 5",
57 | args: args{
58 | check: time.Date(2022, 9, 27, 11, 50, 00, 00, time.UTC),
59 | start: time.Date(2022, 9, 27, 11, 20, 00, 00, time.UTC),
60 | end: time.Date(2022, 9, 27, 11, 30, 00, 00, time.UTC),
61 | },
62 | want: false,
63 | },
64 | }
65 | for _, tt := range tests {
66 | t.Run(tt.name, func(t *testing.T) {
67 | if got := TimeInIntervalInclusive(tt.args.check, tt.args.start, tt.args.end); got != tt.want {
68 | t.Errorf("TimeInIntervalInclusive() = %v, want %v", got, tt.want)
69 | }
70 | })
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/db/sql/role/role.identifier.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.identifier_find(
2 | _id uuid DEFAULT NULL,
3 | _name VARCHAR(256) DEFAULT NULL,
4 | _color VARCHAR(256) DEFAULT NULL,
5 | _lead_id uuid DEFAULT NULL,
6 | _date_added TIMESTAMP DEFAULT NULL,
7 | _permissions JSONB DEFAULT NULL -- Must only contain role_ids not user_ids
8 | )
9 | RETURNS TABLE (
10 | id uuid,
11 | name VARCHAR(256),
12 | color VARCHAR(256),
13 | lead_id uuid,
14 | date_added TIMESTAMP
15 | ) AS
16 | $$
17 | BEGIN
18 | CREATE TEMP TABLE _permissions_table (
19 | permission_type permission.type,
20 | permission_category permission.category,
21 | permission_tenant permission.tenant,
22 | permission_tenant_id uuid
23 | );
24 |
25 | INSERT INTO _permissions_table (
26 | SELECT
27 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
28 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
29 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
30 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
31 | );
32 |
33 | RETURN QUERY
34 | WITH permitted_roles AS (
35 | SELECT permission_tenant_id FROM _permissions_table
36 | WHERE permission_type = 'VIEW'::permission.type
37 | AND permission_category = 'ROLE'::permission.category
38 | AND permission_tenant = 'IDENTIFIER'::permission.tenant
39 | )
40 | SELECT i.id, i.name, i.color, i.lead_id, i.date_added
41 | FROM role.identifier as i
42 | WHERE (EXISTS(SELECT 1 FROM permitted_roles WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_roles))
43 | AND (_id IS NULL OR i.id = _id)
44 | AND (_name IS NULL OR i.name = _name)
45 | AND (_color IS NULL OR i.color = _color)
46 | AND (_lead_id IS NULL OR i.lead_id = _lead_id)
47 | AND (_date_added IS NULL OR i.date_added >= _date_added);
48 |
49 | DROP TABLE _permissions_table;
50 | END
51 | $$ LANGUAGE plpgsql;
52 |
--------------------------------------------------------------------------------
/client/web/example/src/pages/Roles.js:
--------------------------------------------------------------------------------
1 | import Navbar from '../components/Navbar/Navbar.js'
2 | import Footer from "../components/Footer"
3 | import Button from 'react-bootstrap/Button'
4 | import { useState, useEffect, useContext } from 'react';
5 | import RoleListItem from '../components/Role/RoleListItem';
6 | import { useNavigate } from 'react-router-dom';
7 | import { UserContext } from '../App.js';
8 |
9 | function Roles()
10 | {
11 | const [roles, SetRoles] = useState([]);
12 |
13 | const navigate = useNavigate();
14 |
15 | const {userData} = useContext(UserContext);
16 |
17 | //POST request
18 | const FetchRoles = () =>
19 | {
20 | fetch("http://localhost:8080/api/role/information",
21 | {
22 | method: "POST",
23 | mode: "cors",
24 | body: JSON.stringify({
25 | }),
26 | headers:{
27 | 'Content-Type': 'application/json',
28 | 'Authorization': `bearer ${userData.token}` //Changed for frontend editing .token
29 | }
30 | }).then((res) => res.json()).then(data =>
31 | {
32 | SetRoles(data);
33 | });
34 | }
35 |
36 | const AddRole = () =>
37 | {
38 | navigate("/role-create");
39 | }
40 |
41 | //Using useEffect hook. This will send the POST request once the component is mounted
42 | useEffect(() =>
43 | {
44 | FetchRoles()
45 | }, [])
46 |
47 | return (
48 |
49 |
50 |
51 |
52 | {roles.length > 0 && (
53 | roles.map(role =>
54 | {
55 | return
56 | }
57 | )
58 | )}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default Roles
--------------------------------------------------------------------------------
/go/api/security/validate.go:
--------------------------------------------------------------------------------
1 | package security
2 |
3 | import (
4 | "api/data"
5 | "api/db"
6 | "api/redis"
7 | "fmt"
8 | "lib/logger"
9 | "lib/utils"
10 | "net/http"
11 | )
12 |
13 | type HandlerFunc func(http.ResponseWriter, *http.Request, *data.Permissions)
14 | type HandlerFuncOut func(http.ResponseWriter, *http.Request)
15 |
16 | // Validate validates if the sender has permissions to execute the endpoint
17 | func Validate(function HandlerFunc, permissionRequired *data.Permissions) HandlerFuncOut {
18 | return func(writer http.ResponseWriter, request *http.Request) {
19 | access, err := db.Open()
20 | if err != nil {
21 | utils.InternalServerError(writer, request, err)
22 | return
23 | }
24 | defer access.Close()
25 |
26 | // if the user does not require permissions
27 | if permissionRequired == nil {
28 | function(writer, request, nil)
29 | return
30 | }
31 | redisUserData, err := redis.GetRequestRedisData(request)
32 | if err != nil || redisUserData == nil {
33 | logger.Error.Println(err)
34 | utils.BadRequest(writer, request, "Invalid Authorization Token")
35 | return
36 | }
37 | user_id := redisUserData.User_id
38 | // user_id := "00000000-0000-0000-0000-000000000000"
39 | permissions, err := GetUserPermissions(&user_id, access)
40 | if err != nil {
41 | utils.InternalServerError(writer, request, err)
42 | return
43 | }
44 |
45 | // filter permissions based on the permission required
46 | var filteredPermissions data.Permissions
47 | for _, permission := range permissions {
48 |
49 | // logger.Access.Printf("%v %v %v %v\n", *permission.PermissionIdType, *permission.PermissionType, *permission.PermissionCategory, *permission.PermissionTenant)
50 |
51 | if permissionRequired.CompareTo(permission) {
52 | filteredPermissions = append(filteredPermissions, permission)
53 | }
54 | }
55 | if len(filteredPermissions) == 0 {
56 | utils.AccessDenied(writer, request, fmt.Errorf("the user does not have permission to execute query")) // TODO [KP]: Be more descriptive
57 | return
58 | }
59 | function(writer, request, &filteredPermissions)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/go/scheduler/ga/ga_test.go:
--------------------------------------------------------------------------------
1 | package ga
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestIndividual_String(t *testing.T) {
8 | tests := []struct {
9 | name string
10 | individual Individual
11 | notWant string
12 | }{
13 | {
14 | name: "Test 1",
15 | individual: Individual{
16 | Gene: [][]string{
17 | {"A", "B", "C"},
18 | {"1", "2", "3"},
19 | },
20 | },
21 | notWant: "",
22 | },
23 | }
24 | for _, tt := range tests {
25 | t.Run(tt.name, func(t *testing.T) {
26 | if got := tt.individual.String(); got == tt.notWant {
27 | t.Errorf("Individual.String() = %v, want %v", got, tt.notWant)
28 | }
29 | })
30 | }
31 | }
32 |
33 | func TestIndividual_StringDomain(t *testing.T) {
34 | type args struct {
35 | domain Domain
36 | }
37 | tests := []struct {
38 | name string
39 | individual *Individual
40 | args args
41 | notWant string
42 | }{
43 | {
44 | name: "Test 1",
45 | args: args{
46 | domain: Domain{},
47 | },
48 | individual: &Individual{
49 | Gene: [][]string{
50 | {"A", "B", "C"},
51 | {"1", "2", "3"},
52 | },
53 | },
54 | notWant: "",
55 | },
56 | }
57 | for _, tt := range tests {
58 | t.Run(tt.name, func(t *testing.T) {
59 | if got := tt.individual.StringDomain(tt.args.domain); got == tt.notWant {
60 | t.Errorf("Individual.StringDomain() = %v, want %v", got, tt.notWant)
61 | }
62 | })
63 | }
64 | }
65 |
66 | func Test_printGAGraphs(t *testing.T) {
67 | type args struct {
68 | multiplier float64
69 | maxMultiplier float64
70 | avg float64
71 | maxFitness float64
72 | minFitness float64
73 | }
74 | tests := []struct {
75 | name string
76 | args args
77 | }{
78 | {
79 | name: "Test 1",
80 | args: args{
81 | multiplier: 1.0,
82 | maxMultiplier: 1.0,
83 | avg: 1.0,
84 | maxFitness: 1.0,
85 | minFitness: 1.0,
86 | },
87 | },
88 | }
89 | for _, tt := range tests {
90 | t.Run(tt.name, func(t *testing.T) {
91 | printGAGraphs(tt.args.multiplier, tt.args.maxMultiplier, tt.args.avg, tt.args.maxFitness, tt.args.minFitness)
92 | })
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/go/lib/go.mod:
--------------------------------------------------------------------------------
1 | module lib
2 |
3 | go 1.18
4 |
5 | replace api => ../api
6 |
7 | require (
8 | api v0.0.0-00010101000000-000000000000
9 | github.com/golang-migrate/migrate/v4 v4.15.2
10 | github.com/lib/pq v1.10.6
11 | github.com/ory/dockertest/v3 v3.9.1
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/pmezard/go-difflib v1.0.0 // indirect
17 | gopkg.in/yaml.v3 v3.0.1 // indirect
18 | )
19 |
20 | require (
21 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
22 | github.com/Microsoft/go-winio v0.5.2 // indirect
23 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
24 | github.com/cenkalti/backoff/v4 v4.1.3 // indirect
25 | github.com/containerd/continuity v0.3.0 // indirect
26 | github.com/docker/cli v20.10.16+incompatible // indirect
27 | github.com/docker/docker v20.10.13+incompatible // indirect
28 | github.com/docker/go-connections v0.4.0 // indirect
29 | github.com/docker/go-units v0.4.0 // indirect
30 | github.com/gogo/protobuf v1.3.2 // indirect
31 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
32 | github.com/hashicorp/errwrap v1.1.0 // indirect
33 | github.com/hashicorp/go-multierror v1.1.1 // indirect
34 | github.com/imdario/mergo v0.3.12 // indirect
35 | github.com/mitchellh/mapstructure v1.4.1 // indirect
36 | github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
37 | github.com/opencontainers/go-digest v1.0.0 // indirect
38 | github.com/opencontainers/image-spec v1.0.2 // indirect
39 | github.com/opencontainers/runc v1.1.2 // indirect
40 | github.com/pkg/errors v0.9.1 // indirect
41 | github.com/sirupsen/logrus v1.8.1 // indirect
42 | github.com/stretchr/testify v1.8.0
43 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
44 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
45 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect
46 | go.uber.org/atomic v1.7.0 // indirect
47 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
48 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
49 | gopkg.in/yaml.v2 v2.4.0 // indirect
50 | )
51 |
--------------------------------------------------------------------------------
/client/web/example/src/img/room_add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db/sql/role/role.user.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION role.user_find(
2 | _role_id uuid DEFAULT NULL,
3 | _user_id uuid DEFAULT NULL,
4 | _date_added TIMESTAMP DEFAULT NULL,
5 | _permissions JSONB DEFAULT NULL -- user_ids and role_ids
6 | )
7 | RETURNS TABLE (
8 | role_id uuid,
9 | user_id uuid,
10 | date_added TIMESTAMP
11 | ) AS
12 | $$
13 | BEGIN
14 | CREATE TEMP TABLE _permissions_table (
15 | permission_type permission.type,
16 | permission_category permission.category,
17 | permission_tenant permission.tenant,
18 | permission_tenant_id uuid
19 | );
20 |
21 | INSERT INTO _permissions_table (
22 | SELECT
23 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
24 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
25 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
26 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
27 | );
28 |
29 | RETURN QUERY
30 | WITH permitted_users AS (
31 | SELECT permission_tenant_id FROM _permissions_table
32 | WHERE permission_type = 'VIEW'::permission.type
33 | AND permission_category = 'USER'::permission.category
34 | AND permission_tenant = 'ROLE'::permission.tenant
35 | ),
36 | permitted_roles AS (
37 | SELECT permission_tenant_id FROM _permissions_table
38 | WHERE permission_type = 'VIEW'::permission.type
39 | AND permission_category = 'ROLE'::permission.category
40 | AND permission_tenant = 'USER'::permission.tenant
41 | )
42 | SELECT i.role_id, i.user_id, i.date_added
43 | FROM role.user as i
44 | WHERE (EXISTS(SELECT 1 FROM permitted_users WHERE permission_tenant_id is null) OR i.user_id = ANY(SELECT * FROM permitted_users))
45 | AND (EXISTS(SELECT 1 FROM permitted_roles WHERE permission_tenant_id is null) OR i.role_id = ANY(SELECT * FROM permitted_roles))
46 | AND (_role_id IS NULL OR i.role_id = _role_id)
47 | AND (_user_id IS NULL OR i.user_id = _user_id)
48 | AND (_date_added IS NULL OR i.date_added >= _date_added);
49 |
50 | DROP TABLE _permissions_table;
51 | END
52 | $$ LANGUAGE plpgsql;
53 |
--------------------------------------------------------------------------------
/db/sql/team/team.user.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.user_find(
2 | _team_id uuid DEFAULT NULL,
3 | _user_id uuid DEFAULT NULL,
4 | _date_added TIMESTAMP DEFAULT NULL,
5 | _permissions JSONB DEFAULT NULL -- user_ids and team_ids
6 | )
7 | RETURNS TABLE (
8 | team_id uuid,
9 | user_id uuid,
10 | date_added TIMESTAMP
11 | ) AS
12 | $$
13 | BEGIN
14 | CREATE TEMP TABLE _permissions_table (
15 | permission_type permission.type,
16 | permission_category permission.category,
17 | permission_tenant permission.tenant,
18 | permission_tenant_id uuid
19 | );
20 |
21 | INSERT INTO _permissions_table (
22 | SELECT
23 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
24 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
25 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
26 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
27 | );
28 |
29 | RETURN QUERY
30 | WITH permitted_users AS (
31 | SELECT permission_tenant_id FROM _permissions_table
32 | WHERE permission_type = 'VIEW'::permission.type
33 | AND permission_category = 'USER'::permission.category
34 | AND permission_tenant = 'TEAM'::permission.tenant
35 | ),
36 | permitted_teams AS (
37 | SELECT permission_tenant_id FROM _permissions_table
38 | WHERE permission_type = 'VIEW'::permission.type
39 | AND permission_category = 'TEAM'::permission.category
40 | AND permission_tenant = 'USER'::permission.tenant
41 | )
42 | SELECT i.team_id, i.user_id, i.date_added
43 | FROM team.user as i
44 | WHERE (EXISTS(SELECT 1 FROM permitted_users WHERE permission_tenant_id is null) OR i.user_id = ANY(SELECT * FROM permitted_users))
45 | AND (EXISTS(SELECT 1 FROM permitted_teams WHERE permission_tenant_id is null) OR i.team_id = ANY(SELECT * FROM permitted_teams))
46 | AND (_team_id IS NULL OR i.team_id = _team_id)
47 | AND (_user_id IS NULL OR i.user_id = _user_id)
48 | AND (_date_added IS NULL OR i.date_added >= _date_added);
49 |
50 | DROP TABLE _permissions_table;
51 | END
52 | $$ LANGUAGE plpgsql;
53 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.room.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.room_find(
2 | _id uuid DEFAULT NULL,
3 | _building_id uuid DEFAULT NULL,
4 | _name VARCHAR(256) DEFAULT NULL,
5 | _xcoord float DEFAULT NULL,
6 | _ycoord float DEFAULT NULL,
7 | _zcoord float DEFAULT NULL,
8 | _dimension VARCHAR(256) DEFAULT NULL,
9 | _permissions JSONB DEFAULT NULL
10 | )
11 | RETURNS TABLE (
12 | id uuid,
13 | building_id uuid,
14 | name VARCHAR(256),
15 | xcoord float,
16 | ycoord float,
17 | zcoord float,
18 | dimension VARCHAR(256)
19 | ) AS
20 | $$
21 | BEGIN
22 | CREATE TEMP TABLE _permissions_table (
23 | permission_type permission.type,
24 | permission_category permission.category,
25 | permission_tenant permission.tenant,
26 | permission_tenant_id uuid
27 | );
28 |
29 | INSERT INTO _permissions_table (
30 | SELECT
31 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
32 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
33 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
34 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
35 | );
36 |
37 | RETURN QUERY
38 | WITH permitted_rooms AS (
39 | SELECT permission_tenant_id FROM _permissions_table
40 | WHERE permission_type = 'VIEW'::permission.type
41 | AND permission_category = 'RESOURCE'::permission.category
42 | AND permission_tenant = 'ROOM'::permission.tenant
43 | )
44 | SELECT i.id, i.building_id, i.name, i.xcoord, i.ycoord, i.zcoord, i.dimension
45 | FROM resource.room as i
46 | WHERE (EXISTS(SELECT 1 FROM permitted_rooms WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_rooms))
47 | AND (_id IS NULL OR i.id = _id)
48 | AND (_building_id IS NULL OR i.building_id = _building_id)
49 | AND (_name IS NULL OR i.name = _name)
50 | AND (_xcoord IS NULL OR i.xcoord = _xcoord)
51 | AND (_ycoord IS NULL OR i.ycoord = _ycoord)
52 | AND (_zcoord IS NULL OR i.zcoord = _zcoord)
53 | AND (_dimension IS NULL OR i.dimension = _dimension);
54 |
55 | DROP TABLE _permissions_table;
56 | END
57 | $$ LANGUAGE plpgsql;
58 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Resources/AddBuilding.js:
--------------------------------------------------------------------------------
1 | import Form from 'react-bootstrap/Form';
2 | import Button from 'react-bootstrap/Button';
3 | import styles from './resources.module.css';
4 | import { useContext, useEffect, useState } from 'react';
5 | import { UserContext } from '../../App';
6 |
7 | const AddBuilding = ({makeDefault, edited}) =>
8 | {
9 | const [name, setName] = useState('');
10 | const [location, setLocation] = useState('');
11 |
12 | const {userData} = useContext(UserContext);
13 |
14 | const AddBuildingSubmit = async () =>
15 | {
16 | fetch("http://localhost:8080/api/resource/building/create",
17 | {
18 | method: "POST",
19 | mode: "cors",
20 | body: JSON.stringify({
21 | id: null,
22 | name: name,
23 | location: location,
24 | dimension: ''
25 | }),
26 | headers:{
27 | 'Content-Type': 'application/json',
28 | 'Authorization': `bearer ${userData.token}`
29 | }
30 | }).then((res) =>
31 | {
32 | if(res.status === 200)
33 | {
34 | alert("Building Successfully Created!");
35 | edited(true);
36 | }
37 | });
38 | }
39 |
40 | useEffect(() =>
41 | {
42 | setName('');
43 | setLocation('');
44 |
45 | }, [makeDefault]);
46 |
47 | return (
48 |
49 |
50 |
51 | Name
52 | setName(e.target.value)}>
53 |
54 |
55 |
56 | Location
57 | setLocation(e.target.value)}>
58 |
59 |
60 |
61 |
62 | );
63 |
64 | }
65 |
66 | export {AddBuilding as AddBuildingForm}
--------------------------------------------------------------------------------
/client/web/example/src/img/room_edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db/sql/user/user.identifier.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION "user".identifier_find(
2 | _id uuid DEFAULT NULL,
3 | _identifier VARCHAR(256) DEFAULT NULL,
4 | _first_name VARCHAR(256) DEFAULT NULL,
5 | _last_name VARCHAR(256) DEFAULT NULL,
6 | _email VARCHAR(256) DEFAULT NULL,
7 | _picture TEXT DEFAULT NULL,
8 | _date_created TIMESTAMP DEFAULT NULL,
9 | _work_from_home BOOLEAN DEFAULT NULL,
10 | _parking parking.type DEFAULT NULL,
11 | _office_days INTEGER DEFAULT NULL,
12 | _preferred_start_time TIME WITHOUT TIME ZONE DEFAULT NULL,
13 | _preferred_end_time TIME WITHOUT TIME ZONE DEFAULT NULL,
14 | _preferred_desk uuid DEFAULT NULL,
15 | _building_id uuid DEFAULT NULL
16 | )
17 | RETURNS TABLE (
18 | id uuid,
19 | identifier VARCHAR(256),
20 | first_name VARCHAR(256),
21 | last_name VARCHAR(256),
22 | email VARCHAR(256),
23 | picture TEXT,
24 | date_created TIMESTAMP,
25 | work_from_home BOOLEAN,
26 | parking parking.type,
27 | office_days INTEGER,
28 | preferred_start_time TIME WITHOUT TIME ZONE,
29 | preferred_end_time TIME WITHOUT TIME ZONE,
30 | preferred_desk uuid,
31 | building_id uuid
32 | ) AS
33 | $$
34 | BEGIN
35 | RETURN QUERY
36 | SELECT i.id, i.identifier, i.first_name, i.last_name, i.email, i.picture, i.date_created, i.work_from_home, i.parking, i.office_days, i.preferred_start_time, i.preferred_end_time, i.preferred_desk, i.building_id
37 | FROM "user".identifier as i
38 | WHERE (_id IS NULL OR i.id = _id)
39 | AND (_identifier IS NULL OR i.identifier = _identifier)
40 | AND (_first_name IS NULL OR i.first_name = _first_name)
41 | AND (_last_name IS NULL OR i.last_name = _last_name)
42 | AND (_email IS NULL OR i.email = _email)
43 | AND (_picture IS NULL OR i.picture = _picture)
44 | AND (_date_created IS NULL OR i.date_created >= _date_created)
45 | AND (_work_from_home IS NULL OR i.work_from_home = _work_from_home)
46 | AND (_parking IS NULL OR i.parking = _parking)
47 | AND (_office_days IS NULL OR i.office_days = _office_days)
48 | AND (_preferred_start_time IS NULL OR i.preferred_start_time = _preferred_start_time)
49 | AND (_preferred_end_time IS NULL OR i.preferred_end_time = _preferred_end_time)
50 | AND (_preferred_desk IS NULL OR i.preferred_desk = _preferred_desk)
51 | AND (_building_id IS NULL OR i.building_id = _building_id);
52 | END
53 | $$ LANGUAGE plpgsql;
54 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Resources/AddRoom.js:
--------------------------------------------------------------------------------
1 | import Form from 'react-bootstrap/Form';
2 | import Button from 'react-bootstrap/Button';
3 | import styles from './resources.module.css';
4 | import { useContext, useEffect, useState } from 'react';
5 | import { UserContext } from '../../App';
6 |
7 | const AddRoom = ({makeDefault, edited, buildingID}) =>
8 | {
9 | const [name, setName] = useState('');
10 | const [floor, setFloor] = useState('0');
11 |
12 | const {userData} = useContext(UserContext);
13 |
14 | const AddRoomSubmit = async () =>
15 | {
16 | fetch("http://localhost:8080/api/resource/room/create",
17 | {
18 | method: "POST",
19 | mode: "cors",
20 | body: JSON.stringify({
21 | id: null,
22 | building_id: buildingID,
23 | name: name,
24 | xcoord: 0,
25 | ycoord: 0,
26 | zcoord: parseInt(floor),
27 | dimension: ''
28 | }),
29 | headers:{
30 | 'Content-Type': 'application/json',
31 | 'Authorization': `bearer ${userData.token}`
32 | }
33 | }).then((res) =>
34 | {
35 | if(res.status === 200)
36 | {
37 | alert("Room Successfully Created!");
38 | edited(true);
39 | }
40 | });
41 | }
42 |
43 | useEffect(() =>
44 | {
45 | setName('');
46 | setFloor('0');
47 |
48 | }, [makeDefault]);
49 |
50 | return (
51 |
52 |
53 |
54 | Name
55 | setName(e.target.value)}>
56 |
57 |
58 |
59 | Floor
60 | setFloor(e.target.value)}>
61 |
62 |
63 |
64 |
65 | );
66 |
67 | }
68 |
69 | export {AddRoom as AddRoomForm}
--------------------------------------------------------------------------------
/go/api/endpoints/notification.go:
--------------------------------------------------------------------------------
1 | package endpoints
2 |
3 | import (
4 | "api/data"
5 | "fmt"
6 | "lib/utils"
7 | "net/http"
8 | "net/smtp"
9 | "os"
10 |
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | func NotificationHandlers(router *mux.Router) error {
15 | router.HandleFunc("/send", SendNotificationHandler).Methods("POST")
16 | return nil
17 | }
18 |
19 | func SendNotificationHandler(writer http.ResponseWriter, request *http.Request) {
20 |
21 | var notification data.Notification
22 | err := utils.UnmarshalJSON(writer, request, ¬ification)
23 | if err != nil {
24 | utils.BadRequest(writer, request, "invalid_request")
25 | return
26 | }
27 |
28 | //Sender data
29 | from := os.Getenv("SENDER")
30 | password := os.Getenv("PASSWORD")
31 |
32 | //Receiver data
33 | to := []string{
34 | *notification.To,
35 | }
36 |
37 | //smtp Server
38 | smtpHost := "smtp.gmail.com"
39 | smptPort := "587"
40 |
41 | //Message
42 | message := []byte("From: archecapstoneteam@gmail.com\r\n" +
43 | "To: " + *notification.To + "\r\n" +
44 | "Subject: Booking Confirmation\r\n\r\n" +
45 | "Your booking has been confirmed!\n\n" +
46 | "Start Date: " + *notification.StartDate + "\n" +
47 | "Start Time: " + *notification.StartTime + "\n" +
48 | "End Date: " + *notification.EndDate + "\n" +
49 | "End Time: " + *notification.EndTime + "\r\n")
50 |
51 | //Authentication
52 | auth := smtp.PlainAuth("", from, password, smtpHost)
53 |
54 | //Sending email
55 | err = smtp.SendMail(smtpHost+":"+smptPort, auth, from, to, message)
56 | if err != nil {
57 | fmt.Println(err)
58 | return
59 | }
60 |
61 | fmt.Println("Notification email sent")
62 |
63 | utils.Ok(writer, request)
64 | }
65 |
66 | func SendNotification(message string, recipient string) {
67 | //Receiver data
68 | to := []string{
69 | recipient,
70 | }
71 |
72 | //Sender data
73 | from := os.Getenv("SENDER")
74 | password := os.Getenv("PASSWORD")
75 |
76 | //smtp Server
77 | smtpHost := "smtp.gmail.com"
78 | smptPort := "587"
79 |
80 | //Message
81 | theMessage := []byte("From: archecapstoneteam@gmail.com\r\n" +
82 | "To: " + recipient + "\r\n" + message)
83 |
84 | //Authentication
85 | auth := smtp.PlainAuth("", from, password, smtpHost)
86 |
87 | //Sending email
88 | err := smtp.SendMail(smtpHost+":"+smptPort, auth, from, to, theMessage)
89 | if err != nil {
90 | fmt.Println(err)
91 | return
92 | }
93 |
94 | fmt.Println("Notification email sent")
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/db/sql/team/team.identifier.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION team.identifier_find(
2 | _id uuid DEFAULT NULL,
3 | _name VARCHAR(256) DEFAULT NULL,
4 | _color VARCHAR(256) DEFAULT NULL,
5 | _capacity INT DEFAULT NULL,
6 | _picture TEXT DEFAULT NULL,
7 | _priority INT DEFAULT NULL,
8 | _team_lead_id uuid DEFAULT NULL,
9 | _date_created TIMESTAMP DEFAULT NULL,
10 | _permissions JSONB DEFAULT NULL -- Must only contain role_ids not user_ids
11 | )
12 | RETURNS TABLE (
13 | id uuid,
14 | name VARCHAR(256),
15 | color VARCHAR(256),
16 | capacity INT,
17 | picture TEXT,
18 | priority INT,
19 | team_lead_id uuid,
20 | date_created TIMESTAMP
21 | ) AS
22 | $$
23 | BEGIN
24 | CREATE TEMP TABLE _permissions_table (
25 | permission_type permission.type,
26 | permission_category permission.category,
27 | permission_tenant permission.tenant,
28 | permission_tenant_id uuid
29 | );
30 |
31 | INSERT INTO _permissions_table (
32 | SELECT
33 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
34 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
35 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
36 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
37 | );
38 |
39 | RETURN QUERY
40 | WITH permitted_teams AS (
41 | SELECT permission_tenant_id FROM _permissions_table
42 | WHERE permission_type = 'VIEW'::permission.type
43 | AND permission_category = 'TEAM'::permission.category
44 | AND permission_tenant = 'IDENTIFIER'::permission.tenant
45 | )
46 | SELECT i.id, i.name, i.color, i.capacity, i.picture, i.priority, i.team_lead_id, i.date_created
47 | FROM team.identifier as i
48 | WHERE (EXISTS(SELECT 1 FROM permitted_teams WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_teams))
49 | AND (_id IS NULL OR i.id = _id)
50 | AND (_name IS NULL OR i.name = _name)
51 | AND (_color IS NULL OR i.color = _color)
52 | AND (_capacity IS NULL OR i.capacity = _capacity)
53 | AND (_picture IS NULL OR i.picture = _picture)
54 | AND (_priority IS NULL OR i.priority = _priority)
55 | AND (_team_lead_id IS NULL OR i.team_lead_id = _team_lead_id)
56 | AND (_date_created IS NULL OR i.date_created >= _date_created);
57 |
58 | DROP TABLE _permissions_table;
59 | END
60 | $$ LANGUAGE plpgsql;
61 |
--------------------------------------------------------------------------------
/client/web/example/src/img/building_add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Profile/__test__/UserListItem.test.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter } from 'react-router-dom'
2 | import React, { useContext, useState } from 'react'
3 | import UserListItem from '../UserListItem.js'
4 | import {act, render, fireEvent, cleanup, screen} from '@testing-library/react';
5 |
6 | import * as router from 'react-router'
7 |
8 | const navigate = jest.fn();
9 |
10 | const MockUserListItem = ({id, name, email}) => {
11 | return(
12 |
13 |
14 |
15 | )
16 | };
17 |
18 | beforeEach(() => {
19 | jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate);
20 | jest.spyOn(window.localStorage.__proto__, 'setItem');
21 | });
22 |
23 | afterEach(cleanup);
24 |
25 | it('should display the name', () => {
26 | render( );
27 |
28 | expect(screen.getByText('test_name')).toBeInTheDocument();
29 | });
30 |
31 | describe('When clicking on popup', () => {
32 | it('should navigate to /user-edit', () => {
33 | render( );
34 |
35 | expect(screen.getByTestId('resource-edit-icon')).toBeInTheDocument();
36 | fireEvent.click(screen.getByTestId('resource-edit-icon'));
37 | expect(navigate).toHaveBeenCalledWith('/user-edit');
38 | });
39 |
40 | it('should set UserID in local storage', () => {
41 | render( );
42 |
43 | expect(screen.getByTestId('resource-edit-icon')).toBeInTheDocument();
44 | fireEvent.click(screen.getByTestId('resource-edit-icon'));
45 | expect(localStorage.setItem).toHaveBeenCalledWith('UserID', 'test_id');
46 | });
47 |
48 | it('should set UserName in local storage', () => {
49 | render( );
50 |
51 | expect(screen.getByTestId('resource-edit-icon')).toBeInTheDocument();
52 | fireEvent.click(screen.getByTestId('resource-edit-icon'));
53 | expect(localStorage.setItem).toHaveBeenCalledWith('UserName', 'test_name');
54 | });
55 |
56 | it('should set UserEmail in local storage', () => {
57 | render( );
58 |
59 | expect(screen.getByTestId('resource-edit-icon')).toBeInTheDocument();
60 | fireEvent.click(screen.getByTestId('resource-edit-icon'));
61 | expect(localStorage.setItem).toHaveBeenCalledWith('UserEmail', 'test_email');
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/go/scheduler/ga/domain.go:
--------------------------------------------------------------------------------
1 | package ga
2 |
3 | import (
4 | cu "lib/collectionutils"
5 | "lib/utils"
6 | "scheduler/data"
7 | )
8 |
9 | // Domain represents domain information to the problem
10 | type Domain struct {
11 | // Weekly scheduler: User array with user id's duplicated for amount per week
12 | Terminals []string
13 |
14 | Config *data.Config
15 | SchedulerData *data.SchedulerData
16 | Map map[int](string)
17 | InverseMap map[string]([]int) // Array due to the assumption Map may have a many-to-one relationship
18 | // If true, existing bookings may be rescheduled
19 | Reschedule bool
20 | }
21 |
22 | func (domain *Domain) GetRandomTerminal() string {
23 | return domain.Terminals[utils.RandInt(0, len(domain.Terminals))]
24 | }
25 |
26 | func (domain *Domain) GetRandomTerminalArrays(length int) []string {
27 | var result []string
28 | for i := 0; i < length; i++ {
29 | result = append(result, domain.GetRandomTerminal())
30 | }
31 | return result
32 | }
33 |
34 | // Gets unique* elements from the terminals array
35 | // unique in this context means that it will not take the exact same element twice,
36 | // however if duplicates are present, it could happen that an element gets selected twice
37 | // if len(terminals) < length, panic will result
38 | func (domain *Domain) GetRandomUniqueTerminalArrays(length int) []string {
39 | domainTerminalsCopy := cu.Copy1DArr(domain.Terminals)
40 | var result []string
41 | for i := 0; i < length; i++ {
42 | randi := utils.RandInt(0, len(domainTerminalsCopy))
43 | result = append(result, domainTerminalsCopy[randi])
44 | domainTerminalsCopy = cu.RemElemenAtI(domainTerminalsCopy, randi)
45 | }
46 | return result
47 | }
48 |
49 | // A map that contains the user indices per team.
50 | // GetTeamUserIndices gets a map where the keys are teamIds, and the value is an array indicating which indices in a daily individual
51 | // belongs to the tean
52 | func (domain *Domain) GetTeamUserIndices() map[string][]int {
53 | userIndicesMap := domain.InverseMap
54 | teamInfos := domain.SchedulerData.Teams
55 | teamUserIndices := make(map[string][]int) // map[teamId][user indices]
56 | for _, team := range teamInfos { // For each team
57 | teamUserIndices[*team.Id] = make([]int, 0)
58 | for _, userId := range team.UserIds {
59 | // Add the gene index of the user to the team indices array, to indicate which indices in the gene
60 | // map to users part of the team
61 | teamUserIndices[*team.Id] = append(teamUserIndices[*team.Id], userIndicesMap[userId]...)
62 | }
63 | }
64 | return teamUserIndices
65 | }
66 |
--------------------------------------------------------------------------------
/go/scheduler/ga/validate_test.go:
--------------------------------------------------------------------------------
1 | package ga
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestIndividual_CheckIfValidDaily(t *testing.T) {
10 | tests := []struct {
11 | name string
12 | individual *Individual
13 | want bool
14 | }{
15 | {
16 | name: "Test 1",
17 | individual: &Individual{
18 | Gene: [][]string{
19 | {
20 | "A", "B", "C",
21 | },
22 | },
23 | },
24 | want: true,
25 | },
26 | {
27 | name: "Test 1",
28 | individual: &Individual{
29 | Gene: [][]string{
30 | {
31 | "A", "B", "B", "C",
32 | },
33 | },
34 | },
35 | want: false,
36 | },
37 | {
38 | name: "Test 3",
39 | individual: &Individual{
40 | Gene: [][]string{
41 | {},
42 | },
43 | },
44 | want: true,
45 | },
46 | }
47 | for _, tt := range tests {
48 | t.Run(tt.name, func(t *testing.T) {
49 | if got := tt.individual.CheckIfValidDaily(); got != tt.want {
50 | t.Errorf("Individual.CheckIfValidDaily() = %v, want %v", got, tt.want)
51 | }
52 | })
53 | }
54 | }
55 |
56 | func TestIndividual_ValidateIndividual(t *testing.T) {
57 | type args struct {
58 | domain *Domain
59 | }
60 | tests := []struct {
61 | name string
62 | individual *Individual
63 | args args
64 | }{
65 | {
66 | name: "Test 1",
67 | args: args{
68 | domain: &Domain{
69 | Terminals: []string{"D", "F"},
70 | },
71 | },
72 | individual: &Individual{
73 | Gene: [][]string{
74 | {
75 | "A", "B", "C",
76 | },
77 | },
78 | },
79 | },
80 | {
81 | name: "Test 1",
82 | args: args{
83 | domain: &Domain{
84 | Terminals: []string{"D", "F"},
85 | },
86 | },
87 | individual: &Individual{
88 | Gene: [][]string{
89 | {
90 | "A", "B", "B", "C", "C",
91 | },
92 | },
93 | },
94 | },
95 | {
96 | name: "Test 3",
97 | args: args{
98 | domain: &Domain{
99 | Terminals: []string{"D", "F"},
100 | },
101 | },
102 | individual: &Individual{
103 | Gene: [][]string{
104 | {
105 | "A", "A", "A",
106 | },
107 | },
108 | },
109 | },
110 | }
111 | for _, tt := range tests {
112 | t.Run(tt.name, func(t *testing.T) {
113 | tt.individual.ValidateIndividual(tt.args.domain)
114 | assert.Truef(t, tt.individual.CheckIfValidDaily(), "Expected individual to be valid after call, but got %v", tt.individual)
115 | })
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/client/web/example/src/components/Role/RoleUserList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useContext } from 'react';
2 | import { MdDelete } from 'react-icons/md';
3 | import { useNavigate } from 'react-router-dom';
4 | import { UserContext } from '../../App';
5 |
6 | const RoleUserList = ({id}) =>
7 | {
8 | const [roleName, SetRoleName] = useState("")
9 | const navigate=useNavigate();
10 | const {userData} = useContext(UserContext);
11 | useEffect(() =>
12 | {
13 | fetch("http://localhost:8080/api/role/information",
14 | {
15 | method: "POST",
16 | mode: "cors",
17 | body: JSON.stringify({
18 | id: id
19 | }),
20 | headers:{
21 | 'Content-Type': 'application/json',
22 | 'Authorization': `bearer ${userData.token}` //Changed for frontend editing .token
23 | }
24 | }).then((res) => res.json()).then(data =>
25 | {
26 | SetRoleName(data[0].role_name);
27 | }).catch((err) => console.log(err));
28 | }, [id])
29 |
30 | let DeleteRole = async (e) =>
31 | {
32 | e.preventDefault();
33 | if(window.confirm("Are you sure you want to remove this role?"))
34 | {
35 | try
36 | {
37 | let res = await fetch("http://localhost:8080/api/role/user/remove",
38 | {
39 | method: "POST",
40 | mode: "cors",
41 | body: JSON.stringify({
42 | role_id: id,
43 | user_id: window.sessionStorage.getItem("UserID")
44 | }),
45 | headers:{
46 | 'Content-Type': 'application/json',
47 | 'Authorization': `bearer ${userData.token}` //Changed for frontend editing .token
48 | }
49 | });
50 |
51 | if(res.status === 200)
52 | {
53 | alert("Role Successfully Removed!");
54 | navigate(0);
55 | }
56 | }
57 | catch (err)
58 | {
59 | console.log(err);
60 | }
61 | }
62 | }
63 |
64 | return (
65 |
66 |
67 | {roleName}
68 |
69 |
72 |
73 | )
74 | }
75 |
76 | export default RoleUserList
--------------------------------------------------------------------------------
/google_api/main.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import datetime
4 | import os.path
5 |
6 | from google.auth.transport.requests import Request
7 | from google.oauth2.credentials import Credentials
8 | from google_auth_oauthlib.flow import InstalledAppFlow
9 | from googleapiclient.discovery import build
10 | from googleapiclient.errors import HttpError
11 | import json
12 |
13 | # If modifying these scopes, delete the file token.json.
14 | SCOPES = ['https://www.googleapis.com/auth/calendar']
15 |
16 |
17 | def main():
18 | """Shows basic usage of the Google Calendar API.
19 | Prints the start and name of the next 10 events on the user's calendar.
20 | """
21 | creds = None
22 | # The file token.json stores the user's access and refresh tokens, and is
23 | # created automatically when the authorization flow completes for the first
24 | # time.
25 | if not os.path.exists('./login.json') or not os.path.exists('./credentials.json'):
26 | print("Files missing")
27 | return
28 | if os.path.exists('token.json'):
29 | creds = Credentials.from_authorized_user_file('token.json', SCOPES)
30 | # If there are no (valid) credentials available, let the user log in.
31 | if not creds or not creds.valid:
32 | if creds and creds.expired and creds.refresh_token:
33 | creds.refresh(Request())
34 | else:
35 | flow = InstalledAppFlow.from_client_secrets_file(
36 | 'credentials.json', SCOPES)
37 | creds = flow.run_local_server(port=0)
38 | # Save the credentials for the next run
39 | with open('token.json', 'w') as token:
40 | token.write(creds.to_json())
41 |
42 | try:
43 | service = build('calendar', 'v3', credentials=creds)
44 |
45 | # Call the Calendar API
46 | now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
47 | print('Getting the upcoming 10 events')
48 | events_result = service.events().list(calendarId='primary', timeMin=now,
49 | maxResults=10, singleEvents=True,
50 | orderBy='startTime').execute()
51 | events = events_result.get('items', [])
52 |
53 | if not events:
54 | print('No upcoming events found.')
55 | return
56 |
57 | # Prints the start and name of the next 10 events
58 | for event in events:
59 | start = event['start'].get('dateTime', event['start'].get('date'))
60 | print(start, event['summary'])
61 |
62 | except HttpError as error:
63 | print('An error occurred: %s' % error)
64 |
65 | main()
--------------------------------------------------------------------------------
/client/web/example/src/img/building_edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/web/example/src/pages/RolesCreate.js:
--------------------------------------------------------------------------------
1 | import Navbar from '../components/Navbar/Navbar.js'
2 | import Footer from "../components/Footer"
3 | import { useContext, useState } from 'react'
4 | import Form from 'react-bootstrap/Form'
5 | import Button from 'react-bootstrap/Button'
6 | import { useNavigate } from 'react-router-dom';
7 | import { UserContext } from '../App.js'
8 |
9 | const CreateRole = () =>
10 | {
11 | const [roleName, setRoleName] = useState("");
12 | const [roleColor, setRoleColor] = useState("");
13 |
14 | const navigate = useNavigate();
15 | const {userData} = useContext(UserContext);
16 |
17 | let handleSubmit = async (e) =>
18 | {
19 | e.preventDefault();
20 | try
21 | {
22 | let res = await fetch("http://localhost:8080/api/role/create",
23 | {
24 | method: "POST",
25 | mode: "cors",
26 | body: JSON.stringify({
27 | id: null,
28 | role_name: roleName
29 | }),
30 | headers:{
31 | 'Content-Type': 'application/json',
32 | 'Authorization': `bearer ${userData.token}` //Changed for frontend editing .token
33 | }
34 | });
35 |
36 | if(res.status === 200)
37 | {
38 | alert("Role Successfully Created!");
39 | navigate("/role");
40 | }
41 | }
42 | catch(err)
43 | {
44 | console.log(err);
45 | }
46 | };
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
CREATE ROLE
Please enter role details.
54 |
55 |
57 | Role Name
58 | setRoleName(e.target.value)} />
59 |
60 |
61 |
62 | Role Color
63 | setRoleColor(e.target.value)} />
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | )
73 | }
74 |
75 | export default CreateRole
--------------------------------------------------------------------------------
/go/api/scheduler/logger.go:
--------------------------------------------------------------------------------
1 | package scheduler
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 | )
10 |
11 | const SEP string = " ~ "
12 |
13 | type Status string
14 |
15 | const DT_FMT string = "02-01-2006 15:04:05 Monday"
16 |
17 | var (
18 | logPath string = "scheduler.log"
19 | )
20 |
21 | const (
22 | SUCCESS Status = "SUCCESS"
23 | PENDING Status = "PENDING"
24 | FAILED Status = "FAILED"
25 | TIMED_OUT Status = "TIMED_OUT"
26 | )
27 |
28 | type LogEntry struct {
29 | datetime time.Time
30 | status Status
31 | }
32 |
33 | // NewLogEntry Generates a LogEntry struct, if datetime is nil, Now is used
34 | func NewLogEntry(status Status, datetime *time.Time) LogEntry {
35 | now := time.Now() // if no datetime was passed current time will be used
36 | if datetime == nil {
37 | datetime = &now
38 | }
39 | return LogEntry{
40 | status: status,
41 | datetime: *datetime,
42 | }
43 | }
44 |
45 | // Generates string representation
46 | func (entry LogEntry) String() string {
47 | return fmt.Sprintf("%s%s%s", entry.status, SEP, entry.datetime.Format(DT_FMT))
48 | }
49 |
50 | // Parse a string into a struct, representation returned by String is used
51 | func Parse(str string) (*LogEntry, error) {
52 | parts := strings.Split(str, SEP)
53 | status := parts[0]
54 | dateStr := parts[1]
55 | date, err := time.Parse(DT_FMT, dateStr)
56 | if err != nil {
57 | return nil, err
58 | }
59 | entry := NewLogEntry(Status(status), &date)
60 | return &entry, nil
61 | }
62 |
63 | // WriteLog Writes the entry to the passed file
64 | func (entry LogEntry) WriteLog() error {
65 | // func (entry LogEntry) WriteLog(path string) error {
66 | f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) // rw-------
67 | if err != nil {
68 | return err
69 | }
70 | _, err = f.WriteString(entry.String() + "\n")
71 | _ = f.Close()
72 | return err
73 | }
74 |
75 | // ReadLastEntry reads the last entry from a log file and returns the entry, or nil
76 | // if file is empty
77 | func ReadLastEntry() (*LogEntry, error) {
78 | // create the file if it does not yet exist
79 | f, err := os.OpenFile(logPath, os.O_CREATE|os.O_RDONLY, 0600) // rw-------
80 | if err != nil {
81 | return nil, err
82 | }
83 |
84 | var lastLine string // only last line should be returned
85 | scanner := bufio.NewScanner(f)
86 | for scanner.Scan() {
87 | lastLine = scanner.Text()
88 | }
89 |
90 | if err := scanner.Err(); err != nil {
91 | return nil, err
92 | }
93 |
94 | if lastLine == "" {
95 | return nil, nil
96 | }
97 | _ = f.Close()
98 | return Parse(lastLine)
99 | }
100 |
--------------------------------------------------------------------------------
/google_api/main.pyx:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import datetime
4 | import os.path
5 |
6 | from google.auth.transport.requests import Request
7 | from google.oauth2.credentials import Credentials
8 | from google_auth_oauthlib.flow import InstalledAppFlow
9 | from googleapiclient.discovery import build
10 | from googleapiclient.errors import HttpError
11 | import json
12 |
13 | # If modifying these scopes, delete the file token.json.
14 | SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
15 |
16 |
17 | def main():
18 | """Shows basic usage of the Google Calendar API.
19 | Prints the start and name of the next 10 events on the user's calendar.
20 | """
21 | creds = None
22 | # The file token.json stores the user's access and refresh tokens, and is
23 | # created automatically when the authorization flow completes for the first
24 | # time.
25 | if not os.path.exists('./login.json') or not os.path.exists('./credentials.json'):
26 | print("Files missing")
27 | return
28 | if os.path.exists('token.json'):
29 | creds = Credentials.from_authorized_user_file('token.json', SCOPES)
30 | # If there are no (valid) credentials available, let the user log in.
31 | if not creds or not creds.valid:
32 | if creds and creds.expired and creds.refresh_token:
33 | creds.refresh(Request())
34 | else:
35 | flow = InstalledAppFlow.from_client_secrets_file(
36 | 'credentials.json', SCOPES)
37 | creds = flow.run_local_server(port=0)
38 | # Save the credentials for the next run
39 | with open('token.json', 'w') as token:
40 | token.write(creds.to_json())
41 |
42 | try:
43 | service = build('calendar', 'v3', credentials=creds)
44 |
45 | # Call the Calendar API
46 | now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
47 | print('Getting the upcoming 10 events')
48 | events_result = service.events().list(calendarId='primary', timeMin=now,
49 | maxResults=10, singleEvents=True,
50 | orderBy='startTime').execute()
51 | events = events_result.get('items', [])
52 |
53 | if not events:
54 | print('No upcoming events found.')
55 | return
56 |
57 | # Prints the start and name of the next 10 events
58 | for event in events:
59 | start = event['start'].get('dateTime', event['start'].get('date'))
60 | print(start, event['summary'])
61 |
62 | except HttpError as error:
63 | print('An error occurred: %s' % error)
64 |
65 |
--------------------------------------------------------------------------------
/db/sql/booking/booking.meeting_room.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION booking.meeting_room_find(
2 | _id uuid DEFAULT NULL, -- NULLABLE, If supplied try update else insert
3 | _booking_id uuid DEFAULT NULL,
4 | _team_id uuid DEFAULT NULL,
5 | _role_id uuid DEFAULT NULL,
6 | _additional_attendees INT DEFAULT NULL,
7 | _desks_attendees BOOLEAN DEFAULT NULL,
8 | _desks_additional_attendees BOOLEAN DEFAULT NULL,
9 | _permissions JSONB DEFAULT NULL -- Must only contain user_ids not role_ids
10 | )
11 | RETURNS TABLE (
12 | id uuid,
13 | booking_id uuid,
14 | team_id uuid,
15 | role_id uuid,
16 | additional_attendees INT,
17 | desks_attendees BOOLEAN,
18 | desks_additional_attendees BOOLEAN
19 | ) AS
20 | $$
21 | BEGIN
22 | CREATE TEMP TABLE _permissions_table (
23 | permission_type permission.type,
24 | permission_category permission.category,
25 | permission_tenant permission.tenant,
26 | permission_tenant_id uuid
27 | );
28 |
29 | INSERT INTO _permissions_table (
30 | SELECT
31 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
32 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
33 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
34 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
35 | );
36 |
37 | RETURN QUERY
38 | WITH permitted_meeting_rooms AS (
39 | SELECT permission_tenant_id FROM _permissions_table
40 | WHERE permission_type = 'VIEW'::permission.type
41 | AND permission_category = 'BOOKING'::permission.category
42 | AND (permission_tenant = 'USER'::permission.tenant OR permission_tenant = 'ROLE'::permission.tenant OR permission_tenant = 'TEAM'::permission.tenant)
43 | )
44 | SELECT i.id, i.booking_id, i.team_id, i.role_id, i.additional_attendees, i.desks_attendees, i.desks_additional_attendees
45 | FROM booking.meeting_room as i
46 | WHERE (EXISTS(SELECT 1 FROM permitted_meeting_rooms WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_meeting_rooms))
47 | AND (_id IS NULL OR i.id = _id)
48 | AND (_booking_id IS NULL OR i.booking_id = _booking_id)
49 | AND (_team_id IS NULL OR i.team_id = _team_id)
50 | AND (_role_id IS NULL OR i.role_id = _role_id)
51 | AND (_additional_attendees IS NULL OR i.additional_attendees = _additional_attendees)
52 | AND (_desks_attendees IS NULL OR i.desks_attendees = _desks_attendees)
53 | AND (_desks_additional_attendees IS NULL OR i.desks_additional_attendees = _desks_additional_attendees);
54 |
55 | DROP TABLE _permissions_table;
56 | END
57 | $$ LANGUAGE plpgsql;
58 |
--------------------------------------------------------------------------------
/db/sql/resource/resource.identifier.function.find.sql:
--------------------------------------------------------------------------------
1 | CREATE OR REPLACE FUNCTION resource.identifier_find(
2 | _id uuid DEFAULT NULL,
3 | _room_id uuid DEFAULT NULL,
4 | _name VARCHAR(256) DEFAULT NULL,
5 | _xcoord float DEFAULT NULL,
6 | _ycoord float DEFAULT NULL,
7 | _width float DEFAULT NULL,
8 | _height float DEFAULT NULL,
9 | _rotation float DEFAULT NULL,
10 | _resource_type resource.type DEFAULT NULL,
11 | _date_created TIMESTAMP DEFAULT NULL,
12 | _permissions JSONB DEFAULT NULL
13 | )
14 | RETURNS TABLE (
15 | id uuid,
16 | room_id uuid,
17 | name VARCHAR(256),
18 | xcoord float,
19 | ycoord float,
20 | width float,
21 | height float,
22 | rotation float,
23 | resource_type resource.type,
24 | date_created TIMESTAMP,
25 | decorations JSON
26 | ) AS
27 | $$
28 | BEGIN
29 | CREATE TEMP TABLE _permissions_table (
30 | permission_type permission.type,
31 | permission_category permission.category,
32 | permission_tenant permission.tenant,
33 | permission_tenant_id uuid
34 | );
35 |
36 | INSERT INTO _permissions_table (
37 | SELECT
38 | (jsonb_array_elements(_permissions)->>'permission_type')::permission.type,
39 | (jsonb_array_elements(_permissions)->>'permission_category')::permission.category,
40 | (jsonb_array_elements(_permissions)->>'permission_tenant')::permission.tenant,
41 | (jsonb_array_elements(_permissions)->>'permission_tenant_id')::uuid
42 | );
43 |
44 | RETURN QUERY
45 | WITH permitted_identifiers AS (
46 | SELECT permission_tenant_id FROM _permissions_table
47 | WHERE permission_type = 'VIEW'::permission.type
48 | AND permission_category = 'RESOURCE'::permission.category
49 | AND permission_tenant = 'IDENTIFIER'::permission.tenant
50 | )
51 | SELECT i.id, i.room_id, i.name, i.xcoord, i.ycoord, i.width, i.height, i.rotation, i.resource_type, i.date_created, i.decorations
52 | FROM resource.identifier as i
53 | WHERE (EXISTS(SELECT 1 FROM permitted_identifiers WHERE permission_tenant_id is null) OR i.id = ANY(SELECT * FROM permitted_identifiers))
54 | AND (_id IS NULL OR i.id = _id)
55 | AND (_room_id IS NULL OR i.room_id = _room_id)
56 | AND (_name IS NULL OR i.name = _name)
57 | AND (_xcoord IS NULL OR i.xcoord = _xcoord)
58 | AND (_ycoord IS NULL OR i.ycoord = _ycoord)
59 | AND (_width IS NULL OR i.width = _width)
60 | AND (_height IS NULL OR i.height = _height)
61 | AND (_rotation IS NULL OR i.rotation = _rotation)
62 | AND (_resource_type IS NULL OR i.resource_type = _resource_type)
63 | AND (_date_created IS NULL OR i.date_created >= _date_created);
64 |
65 | DROP TABLE _permissions_table;
66 | END
67 | $$ LANGUAGE plpgsql;
--------------------------------------------------------------------------------