diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 5b21d2d..546db64 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/README.md b/README.md index b32951a..af70a80 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ -# mtcg +# MTCG + +This repository is for the 3rd semester project "MonsterTradingCardGame" for software-engineering 1. + +## Setup + +To run the project, modify the connection string in "connection_string.txt" to allow access to the database. The database structure can be recreated with the commands found in setup.sql + +## Structure + +The project consists of two main parts, the HTTP-server, and the actual application. Additionally, there are the database helper functions + +### HTTP Server + +The Server manages routing and resolving of requests, and sends them to the specified services. + +### DB + +The db part is an attempt to simplify database access, but due to some more complex calls this hasn't worked out completely. It still makes the code more readable, and abstracts SQL statements to enums. + +### MTCG + +The last part, the application itself, mostly consists of the Service classes, implementing the Service interface, and the corresponding main classes, which for the most part just interface with the database functions. There are two relevant exceptions: The battle class (and helpers), and the session handler: + +Sessions are one of the few things, that isn't stored in the database, but in memory. Everytime a user makes a request that has to be authenticated, the session handler looks it up in its map, and compares the requirements (admin, username). + +The battle handler is responsible for matching players, and creating a new battle. As multiple users might try to start a battle simultaneously, it needs to be thread-safe, and run the actual battles asynchronously. It implements a queue system, and creates a separate battle instance per two players. Since every new request also creates a new thread, the battle handler only needs to be thread-safe, and only one player runs the actual battle for it to be multithreading. Both players then read from their shared battle class once the battle is complete to retrieve the log. + +## Potential improvements + +- Currently, the exact REST path is discarded after the router resolves it, as multiple functions share the same service class. This could be alleviated by either creating a separate service per route, which however would result in about 20 services, or otherwise parsing the route, which may defeat the point of the service interface, depending on implementation. This inefficiency also results in another issue, the service implementations have quite cluttered switches. + +- As previously mentioned, the db functions are not very complete, and might not improve db access, as some hacks and manually adapting the query is required in quite a few instances. For statements which are fully supported though, it simplifies db calls quite a bit. Depending on IDE the whole system might be obsolete though. + +## Unit tests + +There are two parts of the project which are tested through unit tests: the router and the fight logic. The router is relevant because it allows most other code to rely on different forms of testing, as the router is guaranteed to be working, the fight logic is relevant to test as other testing methods are not precise enough to map an error to a specific part of the code. For other methods integration tests are easier to integrate, and server the same purpose, assuming the database and router are working correctly (hence the unit tests for the router). + +## Lessons learned + +- Starting with the HTTP server made it quite easy testing the application with integration tests (manual using postman or automated using the provided curl script). +- A separate db helper class would probably make sense for larger, more complex projects, but was overkill here. + +## Further notes + +The curl script was modified to automatically store the authentication tokens in variables. + +Time tracking was added directly to the commits for a total of about 60 hours of work; commits without any time are less than 5 minutes. \ No newline at end of file diff --git a/setup.sql b/setup.sql new file mode 100644 index 0000000..c62d237 --- /dev/null +++ b/setup.sql @@ -0,0 +1,224 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 16.0 (Debian 16.0-1.pgdg120+1) +-- Dumped by pg_dump version 16.1 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: cardtype; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.cardtype AS ENUM ( + 'monster', + 'spell' +); + + +ALTER TYPE public.cardtype OWNER TO postgres; + +-- +-- Name: delete_package(); Type: FUNCTION; Schema: public; Owner: postgres +-- + +CREATE FUNCTION public.delete_package() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + DELETE FROM packages + WHERE uuid = OLD.package; + RETURN OLD; +END; +$$; + + +ALTER FUNCTION public.delete_package() OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: cards; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.cards ( + uuid uuid DEFAULT gen_random_uuid() NOT NULL, + damage double precision DEFAULT 0 NOT NULL, + owner uuid, + deck boolean DEFAULT false NOT NULL, + trade boolean DEFAULT false NOT NULL, + package uuid, + name character varying, + CONSTRAINT "trade or deck" CHECK (((deck IS FALSE) OR (trade IS FALSE))) +); + + +ALTER TABLE public.cards OWNER TO postgres; + +-- +-- Name: packages; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.packages ( + uuid uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.packages OWNER TO postgres; + +-- +-- Name: trades; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.trades ( + uuid uuid DEFAULT gen_random_uuid() NOT NULL, + card uuid NOT NULL, + card_type public.cardtype NOT NULL, + min_dmg double precision NOT NULL, + user_uuid uuid +); + + +ALTER TABLE public.trades OWNER TO postgres; + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.users ( + uuid uuid DEFAULT gen_random_uuid() NOT NULL, + username character varying NOT NULL, + password character varying NOT NULL, + name character varying, + bio character varying, + image character varying, + elo integer DEFAULT 100 NOT NULL, + wins integer DEFAULT 0 NOT NULL, + losses integer DEFAULT 0 NOT NULL, + coins integer DEFAULT 20 NOT NULL, + admin boolean DEFAULT false +); + + +ALTER TABLE public.users OWNER TO postgres; + +-- +-- Data for Name: cards; Type: TABLE DATA; Schema: public; Owner: postgres +-- + + + +-- +-- Data for Name: packages; Type: TABLE DATA; Schema: public; Owner: postgres +-- + + + +-- +-- Data for Name: trades; Type: TABLE DATA; Schema: public; Owner: postgres +-- + + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: postgres +-- + + + +-- +-- Name: cards cards_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.cards + ADD CONSTRAINT cards_pk PRIMARY KEY (uuid); + + +-- +-- Name: packages packages_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.packages + ADD CONSTRAINT packages_pk PRIMARY KEY (uuid); + + +-- +-- Name: trades trades_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.trades + ADD CONSTRAINT trades_pk PRIMARY KEY (uuid); + + +-- +-- Name: users user_pk; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT user_pk PRIMARY KEY (uuid); + + +-- +-- Name: users username; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT username UNIQUE (username); + + +-- +-- Name: cards delete_package_trigger; Type: TRIGGER; Schema: public; Owner: postgres +-- + +CREATE TRIGGER delete_package_trigger AFTER UPDATE ON public.cards FOR EACH ROW WHEN (((new.package IS NULL) AND (old.package IS NOT NULL))) EXECUTE FUNCTION public.delete_package(); + + +-- +-- Name: cards cards_packages_uuid_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.cards + ADD CONSTRAINT cards_packages_uuid_fk FOREIGN KEY (package) REFERENCES public.packages(uuid) ON DELETE SET NULL; + + +-- +-- Name: cards cards_users_uuid_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.cards + ADD CONSTRAINT cards_users_uuid_fk FOREIGN KEY (owner) REFERENCES public.users(uuid) ON DELETE SET NULL; + + +-- +-- Name: trades trades_cards_uuid_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.trades + ADD CONSTRAINT trades_cards_uuid_fk FOREIGN KEY (card) REFERENCES public.cards(uuid); + + +-- +-- Name: trades trades_users_uuid_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.trades + ADD CONSTRAINT trades_users_uuid_fk FOREIGN KEY (user_uuid) REFERENCES public.users(uuid); + + +-- +-- PostgreSQL database dump complete +-- +