{"id":19514331,"url":"https://github.com/whoissethdaniel/ttt","last_synced_at":"2025-04-26T06:30:33.619Z","repository":{"id":124104258,"uuid":"141624427","full_name":"WhoIsSethDaniel/ttt","owner":"WhoIsSethDaniel","description":"Practical interview quiz: Write a Tic-Tac-Toe webservice.","archived":true,"fork":false,"pushed_at":"2019-01-08T20:06:48.000Z","size":20,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-25T23:32:13.946Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WhoIsSethDaniel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-19T19:51:00.000Z","updated_at":"2023-05-23T21:44:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"1224bc53-9fb9-479c-a72d-89105ac62f89","html_url":"https://github.com/WhoIsSethDaniel/ttt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhoIsSethDaniel%2Fttt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhoIsSethDaniel%2Fttt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhoIsSethDaniel%2Fttt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhoIsSethDaniel%2Fttt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WhoIsSethDaniel","download_url":"https://codeload.github.com/WhoIsSethDaniel/ttt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250943862,"owners_count":21511639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T23:35:56.317Z","updated_at":"2025-04-26T06:30:33.605Z","avatar_url":"https://github.com/WhoIsSethDaniel.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tic-Tac-Toe Web Service\n\n## PURPOSE\n\nThis was a programming task I was given during an interview. It is not the\ngreatest code, the cleanest code, or the best code. It was written in a very\nshort amount of time (a few hours). But it works!\n\n## HOW TO GET IT\n\nIt is hosted on my GitHub account. Any Git client should be able to clone it, \nbut when you want to run it it's best if it's a Linux host with access to \nplenty of Perl distributions/modules.\n\n```\nhttps://github.com/WhoIsSethDaniel/ttt\n```\n\nTo clone it simply run the following from the command-line:\n\n```\ngit clone git@github.com:WhoIsSethDaniel/ttt.git\n```\n\n## SETTING UP THE ENVIRONMENT\n\nThe Build.PL contains a list of known high-level dependencies for this \nservice. You will need to install them. It is very possible that some \ndependencies were left off the list. I tried to determine any soft dependencies\nand place them in the Build.PL, but I may well have missed a few.\n\nChange to the 'ttt' directory that was created when you cloned the repository.\nAssuming you are on a typical Linux host you can now run the following:\n\n```\n. ./bin/env.sh\n```\n\nThis will setup the environment needed to run the application. env.sh modifies\nor creates two environment variables: PERL5LIB and GAME_TTT_DB_NAME. The former\nis likely familiar to any Perl developer. The latter is a path and filename\nfor where to look for the SQLite database that will contain all the data\nstored by this service.\n\nenv.sh will also create the SQLite database and schema by running the db_create\nscript. This database is only used during 'production' runs of the service or\nwhen you need to perform testing beyond the actual tests. Unless you changed\nenv.sh the database will be in the 'ttt' directory and will be named ttt.db.\nThree users (named 'mark', 'nathan', and 'jeff') were also created.\n\nYou can view the contents of the file using \n\n```\nsqlite3 ttt.db\n```\n\nThe name of the SQLite client varies so it may not be exactly what I typed\nabove. Once you have a SQLite prompt you can type\n\n```\n.tables\n```\n\nTo see a list of tables. You can type\n\n```\n.schema \u003ctable\u003e\n```\n\nTo get a look at the schema for a particular table.\n\nTo exit type\n\n```\n.quit\n```\n\n## HOW TO RUN THE TESTS\n\nIf you are within the 'ttt' directory and you have performed the above step\nyou can now run the tests. Simply do\n\n```\nperl ./Build.PL\n./Build test verbose=1\n```\n\nThis will run all the tests. Failures may represent a problem with the \nlocal Perl environment (missing modules being a primary cause of failure).\n\n### TESTS\n\nI test the model layer and the service itself. The tests that are there are\npretty straightforward. My intention was to show how I would go about writing\ntests and not the quantity of the tests themselves. \n\nThere are no tests for the command-line tools. They are pretty simple tools,\nbut such code should have tests in any real-life deployment.\n\n## DESIGN\n\nThe TTT is a state-ful, session-less web service written in Perl using Dancer2,\nDBIx::Class, and a host of other modules. All known high-level dependencies are\nrecorded in the Build.PL file in the root of the repository.\n\nThe service is designed to allow a registered user to create Tic-Tac-Toe games\nfor either himself and another player, or for creating games for two other\nplayers. There are a set of command-line tools you can use to talk to the\nservice. These tools allow a user to list users, list games they own, create\nnew games, abandon old games, and also allows the user to play games. The\ncommand-line tools are described in more detail later.\n\nAll state for the users and games is stored in a database. Currently this is \na SQLite database (this was briefly described earlier). Given that the model\nis written using DBIx::Class it can be any database that DBI supports.\n\nTTT is a \"thick model - thin controller\" application. Much of the logic (but\nnot currently all) is in the model. Nearly all validation is in the model.\n\n### AUTH \u0026 AUTHZ\n\nThe web service assumes that authentication has already taken place. As of\nright now it expects the user to be transmitted via basic HTTP authentication.\nNo actual authentication is performed. However authz is performed.\n\nThere is a table of users. These are the users allowed to use the service. \nThere is not currently any concept of a super user. \n\nEvery user can:\n* view all users\n* view games he is a player in\n* create games\n* play games in which he is a player\n\n## STARTING THE SERVER\n\nIf you are in the 'ttt' directory and have already run env.sh (as mentioned\nearlier) you can start the server in a terminal\n\n```\nplackup -p 5000 share/ttt.psgi\n```\n\nThis will start to log to the terminal you are in. You may want to run the\nserver in screen or tmux. Or perhaps just have another terminal handy for\nafter you've started the server.\n\nAll logging is to standard out.\n\nThere is no configuration file but adding one via Dancer2 seems pretty \nstraightforward. There is no need for one while running the tests or\nseeing how the app functions in your personal environment.\n\n## COMMAND-LINE TOOLS\n\nThere are six command-line tools. They all start with the 'ttt-' prefix.\nThe tools are\n\n* ttt-list-users\n* ttt-list-games\n* ttt-create-game\n* ttt-leave-game\n* ttt-show-board\n* ttt-play\n\n### COMMON OPTIONS\n\n#### -u\n\nAll tools require that a user name be given when you run the tool. For example, to retrieve a list of users run\n\n```\n./bin/ttt-list-users -u jeff\n```\n\n(when you ran env.sh three users were created: 'jeff', 'mark', and 'nathan',\nuse any of them)\n\n#### -v\n\nAll tools have a -v option. This option will causae the tool to dump the JSON\nreturned from the service.\n\n### ttt-list-users\n\nThe output should be all the users currently known to the service.\n\ne.g.\n```\nttt-list-users -u jeff\n```\n\n### ttt-list-games\n\nThe output will be all games for which your user is a player. It will provide\nbasic details about the game: the player names, their tokens, the width of the\ngame grid (currently artificially limited to 20), the next player to play, and\nthe status of the game.\n\ne.g.\n```\nttt-list-games -u jeff\n```\n\n### ttt-create-game\n\nThis tool allows you to create a new game. From the command-line you can specify\nthe width of the game grid (defaults to 3), the names of the two players, and what token each player will be using. \n\ne.g.\n```\nttt-create-game -u jeff --p1 jeff --p2 mark --token1 o --token2 x\n```\n\n### ttt-leave-game\n\nDon't like the game you're playing? Wuss out and abandon the game. This requires\npassing a 'game id'.  This id is available when listing out the games using\nttt-list-games.\n\ne.g.\n```\nttt-leave-game -u jeff --game 1\n```\n\n### ttt-show-board\n\nThis requires a 'game id'. This id is simply a number that identifies the \ngame you want to see. The 'id' is displayed when you use ttt-list-games.\n\nThis command will show you the game board, which cells in the board have been\nplayed on, and the number of the cell(s) that have yet to be played on.\n\ne.g.\n```\nttt-show-board -u jeff --game 1\n```\n\n### ttt-play\n\nPlay your token on the given game and cell. Use ttt-show-board to see the\ncell information. Once a user has won a game that game will end and will never\nbe playable again.\n\ne.g.\n```\nttt-play -u jeff --game 1 --cell 10\n```\n\n## CHALLENGES\n\nI was originally using Mojolicious as my service framework, but somewhat\nlate in the development decided to use Dancer2. I had one small (but time\nconsuming) problem (since resolved), and one larger problem. Both explained\nbelow.\n\n### DANCER2 CHALLENGE #1\n\nSince I hadn't used Dancer 1 in quite some time and never used Dancer 2 I spent\nsome time going through Dancer2::Manual, Dancer2::Tutorial, Dancer2::Cookbook,\nand a few other perldocs. Since I wanted the service to serialize and deserialize to JSON I did as the documentation suggested. I placed the following in \nService.pm:\n\n```\nset serializer =\u003e 'JSON';\n```\n\nThis seemed to work. All GETs ran perfectly. However I was seeing problems\nwith PUT/POST. Eventually I discovered it was a problem with deserializing\nthe JSON content being passed in to the app. I spent a great deal of time \ntrying different things to get it to work and eventually discovered that,\nat least for *deserializing* the accessor Dancer2 uses for retrieving the\ndeserializer was undefined. After much more time I discovered there is a \nrace condition. The ttt.psgi file looked like this:\n\n```\nuse Dancer2;\nuse Game::TTT::Service;\nuse Plack::Builder;\n\ndance();\n```\n\nI finally discovered that I had to place the \"set serializer =\u003e 'JSON'\" in\n*this* file to get all serialization and deserialization to work. You can\nsee a comment about this in the ttt.psgi file as it is today.\n\n### DANCER2 CHALLENGE #2\n\nIn most 'thick-model / thin-controller' apps, or at least the ones I have\nworked on, the model is somewhat (logically) separated from the controller. The\n'typical' (I assert this without proof beyond my own experience) way to get\nDBIx::Class to notify the controller of a problem is for it to throw an\nexception, have the controller catch it, identify it as a model problem (via\ninspecting the exception class name, typically), and act accordingly. \n\nI have discovered no way to do this in Dancer2. And I have tried. In the end\nit seems that the hooks init_error and begin_error both happen *after* the\nexception has been wrapped in a Dancer2::Core::Error class. I could find\nno way to inject extra metadata into this object. Things I tried:\n\n* using DBIx::Class's exception_action to override how DBIx::Class throws exceptions\n* sub-classing Dancer2::Core::Error\n* using init_error and/or begin_error to capture the raw exception\n* various combinations of the above \n\nAnyway, I had to abandon this idea and there is a nasty hack named\n'model_guard' in the Service that does all the exception handling from the\nmodel.\n\n## KNOWN ISSUES\n\n* not all error messages are very nice\n* some tools will report nothing if you use a user not in the 'users' table\n* no computer player\n* the command-line tools print a confusing 'failed to compile' error message when you use -h, but don't supply all the required options. Haven't looked at this at all.\n* test coverage\n* could do with some refactoring. \n  * I didn't use ResultSets at all. There are a few places in the service where having a ResultSet method would make the code look better.\n  * moving the tic-tac-toe 'winning' logic to a separate module seems like it would be a good idea\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhoissethdaniel%2Fttt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwhoissethdaniel%2Fttt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhoissethdaniel%2Fttt/lists"}