├── 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 |
30 | 31 |
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 |
20 |
21 |
22 |
{name}
23 | 24 |
25 |
26 |
27 |
28 |
29 |
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 | {/*
35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 |
*/} 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 |
70 |
71 |
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 |
56 | 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 |
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; --------------------------------------------------------------------------------