├── README.md ├── course ├── 1-intro │ └── exercises │ │ ├── 1-learn_to_run_code │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-bug │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-compiling_xkcd │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-lang_compare_speed │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5-compiling_code │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-what_is_compiled │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-lang_compare_compiled │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7a-lang_compare_compiled │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7b-lang_compare_compiled │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 8-strongly_typed │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 9-lang_compare_memory │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 9a-lang_compare_memory │ │ ├── multiple_choice.json │ │ └── readme.md ├── 10-advanced_functions │ └── exercises │ │ ├── 1-higher-order │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-higher_order_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2a-higher_order_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3-currying │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4-defer │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-closures │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-clousures_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6a-clousures_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 7-anonymous_functions │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 11-pointers │ └── exercises │ │ ├── 1-pointers_intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-pointers_practice │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-pointers_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3b-pointers_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-nil_dereference │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-pointer_receiver │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 6-pointer_receiver_code │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 12-local_development │ └── exercises │ │ ├── 1-intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 10-go_install │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 10a-go_install │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 11-custom_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 11a-custom_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 11b-custom_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 12-custom_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 12a-custom_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 13-remote_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 13a-remote_package │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 14-library_packages_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 14a-library_packages_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 14b-library_packages_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2-package_naming │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2a-package_naming │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3-help │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-modules │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4a-modules │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4b-modules │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5-gopath │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6-first_program │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6a-first_program │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6b-first_program │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-go_run │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7a-go_run │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 8-go_build │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 8a-go_build │ │ ├── multiple_choice.json │ │ └── readme.md ├── 13-channels │ ├── challenges │ │ └── 1-channels_practice │ │ │ ├── code.go │ │ │ ├── complete.go │ │ │ ├── expected.txt │ │ │ └── readme.md │ └── exercises │ │ ├── 1-intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-channels_deadlock │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-channels_send │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4-buffered_channels │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-close │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-range │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 7-select │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 8-select_default │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 9-channel_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 9a-channel_quiz │ │ ├── multiple_choice.json │ │ └── readme.md ├── 14-mutexes │ └── exercises │ │ ├── 1-mutex │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-mutex_name │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-mutex_review │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4a-mutex_review │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5-rw_mutex │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-rw_mutex_review │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6a-rw_mutex_review │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 6b-rw_mutex_review │ │ ├── multiple_choice.json │ │ └── readme.md ├── 15-generics │ └── exercises │ │ ├── 1-generics │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-generics_why │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2a-generics_why │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2b-generics_why │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3-constraints │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4-interface_type_lists │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5-parametric_constraints │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ └── 6-type_names │ │ ├── multiple_choice.json │ │ └── readme.md ├── 16-go_facts │ └── exercises │ │ ├── 1-proverbs │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 1a-proverbs │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 1b-proverbs │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 1c-proverbs │ │ ├── multiple_choice.json │ │ └── readme.md ├── 2-variables │ └── exercises │ │ ├── 1-basic_types │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 10-conditionals │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 11-if_init │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 2-short_declarations │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-type_inference │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4-same_line_declarations │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-type_sizes │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-which_type_to_use │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6a-which_type_to_use │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-constants │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 8-computed_constants │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ └── 9-formatting_strings │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 3-functions │ └── exercises │ │ ├── 1-intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-multiple_params │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3-declaration_syntax │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3a-declaration_syntax │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3b-declaration_syntax │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-pass_by_value │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-ignoring_return_values │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-named_return_values │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6a-named_returns_explicit │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6b-when_to_name_returns │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6c-when_to_name_returns │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-early_returns │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 7a-early_returns │ │ ├── multiple_choice.json │ │ └── readme.md ├── 4-structs │ └── exercises │ │ ├── 1-intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-nested_structs │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-anonymous_structs │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3a-anonymous_structs │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-embedded_structs │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ └── 5-methods │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 5-interfaces │ └── exercises │ │ ├── 1-interfaces │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-implements │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-implicit │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3a-implicit │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4a-quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5-multiple_interfaces │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 6-naming_args │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 6a-naming_args │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-type_assertion │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 8-type_switch │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 9-clean_interfaces │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 9a-clean_interfaces │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 9b-clean_interfaces │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 9c-clean_interfaces │ │ ├── multiple_choice.json │ │ └── readme.md ├── 6-errors │ └── exercises │ │ ├── 1-errors │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-formatting_strings │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-custom_errors │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4a-errors_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4b-errors_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ └── 5-errors_package │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 7-loops │ └── exercises │ │ ├── 1-intro │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-omit_condition │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-while │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 4-loops_fizzbuzz │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ └── 5-continue_and_break │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md ├── 8-slices │ └── exercises │ │ ├── 1-arrays │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 10-slice_gotcha │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 10a-slice_gotcha │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 10b-slice_gotcha │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 11-range │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 2-slices │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 3-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3a-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 3b-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 4-slices_no_array │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 5-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5a-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 5b-slices_quiz │ │ ├── multiple_choice.json │ │ └── readme.md │ │ ├── 7-variadic_functions │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ ├── 8-append │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md │ │ └── 9-2d_slices │ │ ├── code.go │ │ ├── complete.go │ │ ├── expected.txt │ │ └── readme.md └── 9-maps │ └── exercises │ ├── 1-maps │ ├── code.go │ ├── complete.go │ ├── expected.txt │ └── readme.md │ ├── 2-mutating_maps │ ├── code.go │ ├── complete.go │ ├── expected.txt │ └── readme.md │ ├── 3-map_keys │ ├── multiple_choice.json │ └── readme.md │ ├── 3a-map_keys │ ├── multiple_choice.json │ └── readme.md │ ├── 4-maps_count │ ├── code.go │ ├── complete.go │ ├── expected.txt │ └── readme.md │ ├── 5a-maps_quiz │ ├── multiple_choice.json │ └── readme.md │ ├── 5b-maps_quiz │ ├── multiple_choice.json │ └── readme.md │ ├── 5c-maps_quiz │ ├── multiple_choice.json │ └── readme.md │ ├── 5d-maps_quiz │ ├── multiple_choice.json │ └── readme.md │ └── 6-nested_maps │ ├── code.go │ ├── complete.go │ ├── expected.txt │ └── readme.md └── project ├── 1-setup └── readme.md ├── 10-posts ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_feed.go │ ├── handler_feed_follows.go │ ├── handler_posts.go │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ ├── auth │ │ └── auth.go │ └── database │ │ ├── db.go │ │ ├── feed_follows.sql.go │ │ ├── feeds.sql.go │ │ ├── models.go │ │ ├── posts.sql.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── middleware_auth.go │ ├── models.go │ ├── scraper.go │ ├── sql │ ├── queries │ │ ├── feed_follows.sql │ │ ├── feeds.sql │ │ ├── posts.sql │ │ └── users.sql │ └── schema │ │ ├── 001_users.sql │ │ ├── 002_users_apikey.sql │ │ ├── 003_feeds.sql │ │ ├── 004_feed_follows.sql │ │ ├── 005_feed_lastfetched.sql │ │ └── 006_posts.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ ├── google │ │ └── uuid │ │ │ ├── .travis.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── CONTRIBUTORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── dce.go │ │ │ ├── doc.go │ │ │ ├── hash.go │ │ │ ├── marshal.go │ │ │ ├── node.go │ │ │ ├── node_js.go │ │ │ ├── node_net.go │ │ │ ├── null.go │ │ │ ├── sql.go │ │ │ ├── time.go │ │ │ ├── util.go │ │ │ ├── uuid.go │ │ │ ├── version1.go │ │ │ └── version4.go │ ├── joho │ │ └── godotenv │ │ │ ├── .gitignore │ │ │ ├── LICENCE │ │ │ ├── README.md │ │ │ ├── godotenv.go │ │ │ └── parser.go │ └── lib │ │ └── pq │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── TESTS.md │ │ ├── array.go │ │ ├── buf.go │ │ ├── conn.go │ │ ├── conn_go18.go │ │ ├── connector.go │ │ ├── copy.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── error.go │ │ ├── krb.go │ │ ├── notice.go │ │ ├── notify.go │ │ ├── oid │ │ ├── doc.go │ │ └── types.go │ │ ├── rows.go │ │ ├── scram │ │ └── scram.go │ │ ├── ssl.go │ │ ├── ssl_permissions.go │ │ ├── ssl_windows.go │ │ ├── url.go │ │ ├── user_other.go │ │ ├── user_posix.go │ │ ├── user_windows.go │ │ └── uuid.go │ └── modules.txt ├── 11-submit └── readme.md ├── 2-boilerplate ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_ready.go │ ├── json.go │ ├── main.go │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt ├── 3-postgres └── readme.md ├── 4-create_users ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ └── database │ │ ├── db.go │ │ ├── models.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── models.go │ ├── sql │ ├── queries │ │ └── users.sql │ └── schema │ │ └── 001_users.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt ├── 5-apikey ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ ├── auth │ │ └── auth.go │ └── database │ │ ├── db.go │ │ ├── models.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── models.go │ ├── sql │ ├── queries │ │ └── users.sql │ └── schema │ │ ├── 001_users.sql │ │ └── 002_users_apikey.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt ├── 6-createfeed ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_feed.go │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ ├── auth │ │ └── auth.go │ └── database │ │ ├── db.go │ │ ├── feeds.sql.go │ │ ├── models.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── middleware_auth.go │ ├── models.go │ ├── sql │ ├── queries │ │ ├── feeds.sql │ │ └── users.sql │ └── schema │ │ ├── 001_users.sql │ │ ├── 002_users_apikey.sql │ │ └── 003_feeds.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt ├── 7-getfeeds ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_feed.go │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ ├── auth │ │ └── auth.go │ └── database │ │ ├── db.go │ │ ├── feeds.sql.go │ │ ├── models.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── middleware_auth.go │ ├── models.go │ ├── sql │ ├── queries │ │ ├── feeds.sql │ │ └── users.sql │ └── schema │ │ ├── 001_users.sql │ │ ├── 002_users_apikey.sql │ │ └── 003_feeds.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt ├── 8-feedfollows ├── readme.md └── src │ ├── .gitignore │ ├── go.mod │ ├── go.sum │ ├── handler_feed.go │ ├── handler_feed_follows.go │ ├── handler_ready.go │ ├── handler_user.go │ ├── internal │ ├── auth │ │ └── auth.go │ └── database │ │ ├── db.go │ │ ├── feed_follows.sql.go │ │ ├── feeds.sql.go │ │ ├── models.go │ │ └── users.sql.go │ ├── json.go │ ├── main.go │ ├── middleware_auth.go │ ├── models.go │ ├── sql │ ├── queries │ │ ├── feed_follows.sql │ │ ├── feeds.sql │ │ └── users.sql │ └── schema │ │ ├── 001_users.sql │ │ ├── 002_users_apikey.sql │ │ ├── 003_feeds.sql │ │ └── 004_feed_follows.sql │ ├── sqlc.yaml │ └── vendor │ ├── github.com │ ├── go-chi │ │ ├── chi │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── chain.go │ │ │ ├── chi.go │ │ │ ├── context.go │ │ │ ├── mux.go │ │ │ └── tree.go │ │ └── cors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cors.go │ │ │ └── utils.go │ └── joho │ │ └── godotenv │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ └── modules.txt └── 9-scraper ├── readme.md └── src ├── .gitignore ├── go.mod ├── go.sum ├── handler_feed.go ├── handler_feed_follows.go ├── handler_ready.go ├── handler_user.go ├── internal ├── auth │ └── auth.go └── database │ ├── db.go │ ├── feed_follows.sql.go │ ├── feeds.sql.go │ ├── models.go │ └── users.sql.go ├── json.go ├── main.go ├── middleware_auth.go ├── models.go ├── scraper.go ├── sql ├── queries │ ├── feed_follows.sql │ ├── feeds.sql │ └── users.sql └── schema │ ├── 001_users.sql │ ├── 002_users_apikey.sql │ ├── 003_feeds.sql │ ├── 004_feed_follows.sql │ └── 005_feed_lastfetched.sql ├── sqlc.yaml └── vendor ├── github.com ├── go-chi │ ├── chi │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── chain.go │ │ ├── chi.go │ │ ├── context.go │ │ ├── mux.go │ │ └── tree.go │ └── cors │ │ ├── LICENSE │ │ ├── README.md │ │ ├── cors.go │ │ └── utils.go └── joho │ └── godotenv │ ├── .gitignore │ ├── LICENCE │ ├── README.md │ ├── godotenv.go │ └── parser.go └── modules.txt /README.md: -------------------------------------------------------------------------------- 1 | # Assets for "Learn Go" on FreeCodeCamp 2 | 3 | This is a snapshot of the code samples for the ["Learn Go" course](https://boot.dev/courses/learn-golang) on [Boot.dev](https://boot.dev) at the time the video for FreeCodeCamp was released on YouTube. If you want the most up-to-date version of the code, please visit the official [Boot.dev course](https://boot.dev/courses/learn-golang). Otherwise, if you're looking for the files used in the video, you're in the right place! 4 | 5 | * [Course code samples](/course) 6 | * [Project steps](/project) 7 | 8 | ## License 9 | 10 | You are free to use this content and code for personal education purposes. However, you are *not* authorized to publish this content or code elsewhere, whether for commercial purposes or not. 11 | -------------------------------------------------------------------------------- /course/1-intro/exercises/1-learn_to_run_code/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // single-line comments start with "//" 7 | // comments are just for documentation - they don't execute 8 | fmt.Println("hello world") 9 | } 10 | -------------------------------------------------------------------------------- /course/1-intro/exercises/1-learn_to_run_code/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // single-line comments start with "//" 7 | // comments are just for documentation - they don't execute 8 | fmt.Println("starting Textio server") 9 | } 10 | -------------------------------------------------------------------------------- /course/1-intro/exercises/1-learn_to_run_code/expected.txt: -------------------------------------------------------------------------------- 1 | starting Textio server 2 | -------------------------------------------------------------------------------- /course/1-intro/exercises/1-learn_to_run_code/readme.md: -------------------------------------------------------------------------------- 1 | # Welcome to "Learn Go" 2 | 3 | *This course assumes you're familiar with programming basics. If you're new to coding check out our [Learn Python course](https://boot.dev/learn/learn-python) first.* 4 | 5 | ![golang gopher](https://go.dev/blog/gopher/header.jpg) 6 | 7 | ## Booting up the "Textio" server 8 | 9 | All the code you write in this course is part of a larger product: an SMS API called "Textio". Textio sends text messages over the internet, it's basically a cute Twilio clone. 10 | 11 | Assignment: log `starting Textio server` to the console instead of `hello world`. 12 | -------------------------------------------------------------------------------- /course/1-intro/exercises/2-bug/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | messagesFromDoris := []string{ 7 | "You doing anything later??", 8 | "Did you get my last message?", 9 | "Don't leave me hanging...", 10 | "Please respond I'm lonely!", 11 | } 12 | numMessages := float64(len(messagesFromDoris)) 13 | costPerMessage := .02 14 | 15 | // don't touch above this line 16 | 17 | totalCost := costPerMessage + numMessages 18 | 19 | // don't touch below this line 20 | 21 | fmt.Printf("Doris spent %.2f on text messages today\n", totalCost) 22 | } 23 | -------------------------------------------------------------------------------- /course/1-intro/exercises/2-bug/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | messagesFromDoris := []string{ 7 | "You doing anything later??", 8 | "Did you get my last message?", 9 | "Don't leave me hanging...", 10 | "Please respond I'm lonely!", 11 | } 12 | numMessages := float64(len(messagesFromDoris)) 13 | costPerMessage := .02 14 | 15 | // don't touch above this line 16 | 17 | totalCost := costPerMessage * numMessages 18 | 19 | // don't touch below this line 20 | 21 | fmt.Printf("Doris spent %.2f on text messages today\n", totalCost) 22 | } 23 | -------------------------------------------------------------------------------- /course/1-intro/exercises/2-bug/expected.txt: -------------------------------------------------------------------------------- 1 | Doris spent 0.08 on text messages today 2 | -------------------------------------------------------------------------------- /course/1-intro/exercises/2-bug/readme.md: -------------------------------------------------------------------------------- 1 | # Fix a Bug! 2 | 3 | Textio users are reporting that we're billing them for wildly inaccurate amounts. They're *supposed* to be billed `.02` dollars for each text message sent. 4 | 5 | **Fix the math bug on line 17.** 6 | -------------------------------------------------------------------------------- /course/1-intro/exercises/3-compiling_xkcd/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Go code generally runs ____ than interpreted languages and compiles ____ than other compiled languages like C and Rust", 3 | "answers": [ 4 | "faster, faster", 5 | "faster, slower", 6 | "slower, faster", 7 | "slower, slower" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/1-intro/exercises/3-compiling_xkcd/readme.md: -------------------------------------------------------------------------------- 1 | # Go is fast, simple and productive 2 | 3 | Generally speaking, compiled languages run much faster than interpreted languages, and Go is no exception. 4 | 5 | Go is one of the fastest programming languages, beating JavaScript, Python, and Ruby handily in most benchmarks. 6 | 7 | However, Go code doesn't *run* quite as fast as its compiled Rust and C counterparts. That said, it *compiles* much faster than they do, which makes the developer experience super productive. Unfortunately, there are no swordfights on Go teams... 8 | 9 | ![xkcd compiling](https://imgs.xkcd.com/comics/compiling.png) 10 | 11 | *- comic by [xkcd](https://xkcd.com/303/)* 12 | -------------------------------------------------------------------------------- /course/1-intro/exercises/4-lang_compare_speed/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Does Go generally execute faster than Rust?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/4-lang_compare_speed/readme.md: -------------------------------------------------------------------------------- 1 | # Comparing Go's Speed 2 | 3 | Go is *generally* faster and more lightweight than interpreted or VM-powered languages like: 4 | 5 | * Python 6 | * JavaScript 7 | * PHP 8 | * Ruby 9 | * Java 10 | 11 | However, in terms of execution speed, Go does lag behind some other compiled languages like: 12 | 13 | * C 14 | * C++ 15 | * Rust 16 | 17 | Go is a bit slower mostly due to its automated memory management, also known as the "Go runtime". Slightly slower speed is the price we pay for memory safety and simple syntax! 18 | 19 | ![speed comparison](https://miro.medium.com/max/2020/1*nlpYI256BR71xMBWd1nlfg.png) 20 | 21 | Textio is an amazing candidate for a Go project. We'll be able to quickly process large amounts of text all while using a language that is safe and simple to write. 22 | -------------------------------------------------------------------------------- /course/1-intro/exercises/5-compiling_code/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("the-compiled Textio server is starting" 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/5-compiling_code/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("the-compiled Textio server is starting") 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/5-compiling_code/expected.txt: -------------------------------------------------------------------------------- 1 | the-compiled Textio server is starting 2 | -------------------------------------------------------------------------------- /course/1-intro/exercises/6-what_is_compiled/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Do computer processors understand English instructions like 'open the browser'?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/7-lang_compare_compiled/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Do users of compiled programs need access to source code?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/7a-lang_compare_compiled/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which language is interpreted?", 3 | "answers": [ 4 | "Python", 5 | "Rust", 6 | "Go", 7 | "C++" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/1-intro/exercises/7b-lang_compare_compiled/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why is it generally more simple to deploy a compiled server program?", 3 | "answers": [ 4 | "There are no runtime language dependencies", 5 | "Compiled code is faster", 6 | "Compiled code is more memory efficient", 7 | "Because docker exists" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/1-intro/exercises/8-strongly_typed/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var username string = "wagslane" 7 | var password int = 20947382822 8 | 9 | // don't edit below this line 10 | fmt.Println("Authorization: Basic", username+":"+password) 11 | } 12 | -------------------------------------------------------------------------------- /course/1-intro/exercises/8-strongly_typed/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var username string = "wagslane" 7 | var password string = "20947382822" 8 | 9 | // don't edit below this line 10 | fmt.Println("Authorization: Basic", username+":"+password) 11 | } 12 | -------------------------------------------------------------------------------- /course/1-intro/exercises/8-strongly_typed/expected.txt: -------------------------------------------------------------------------------- 1 | Authorization: Basic wagslane:20947382822 2 | -------------------------------------------------------------------------------- /course/1-intro/exercises/9-lang_compare_memory/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Generally speaking, which language uses more memory?", 3 | "answers": [ 4 | "Java", 5 | "Go" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/1-intro/exercises/9a-lang_compare_memory/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What's one of the purposes of the Go runtime?", 3 | "answers": [ 4 | "To cleanup unused memory", 5 | "To compile Go code", 6 | "To style Go code and make it easier to read", 7 | "To cook fried chicken" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/1-higher-order/expected.txt: -------------------------------------------------------------------------------- 1 | * Thanks for getting back to me. -> Thanks for getting back to me. Kind regards. 2 | * Great to see you again. -> Great to see you again. Kind regards. 3 | * I would love to hang out this weekend. -> I would love to hang out this weekend. Kind regards. 4 | * Got any hot stock tips? -> Got any hot stock tips? Kind regards. 5 | ==================================== 6 | * Thanks for getting back to me. -> Hello! Thanks for getting back to me. 7 | * Great to see you again. -> Hello! Great to see you again. 8 | * I would love to hang out this weekend. -> Hello! I would love to hang out this weekend. 9 | * Got any hot stock tips? -> Hello! Got any hot stock tips? 10 | ==================================== 11 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/2-higher_order_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is a higher-order function?", 3 | "answers": [ 4 | "A function that takes another function as an argument", 5 | "A function with superior logic", 6 | "A function that is first in the call stack" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/2a-higher_order_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is a first-class function?", 3 | "answers": [ 4 | "A function that is treated like any other variable", 5 | "A function that has been deemed most important by the architect", 6 | "A function that takes another function as an argument" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/3-currying/expected.txt: -------------------------------------------------------------------------------- 1 | Logs: 2 | Error on database server: out of memory 3 | Error on database server: cpu is pegged 4 | Error on database server: networking issue 5 | Error on database server: invalid syntax 6 | ==================================== 7 | Logs: 8 | Error on mail server, email too large 9 | Error on mail server, non alphanumeric symbols found 10 | ==================================== 11 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/4-defer/expected.txt: -------------------------------------------------------------------------------- 1 | Initial users: 2 | - breanna 3 | - elon 4 | - john 5 | - kade 6 | ==================================== 7 | Attempting to delete john... 8 | Log: admin deleted 9 | ==================================== 10 | Attempting to delete santa... 11 | Log: user not found 12 | ==================================== 13 | Attempting to delete kade... 14 | Log: user deleted 15 | ==================================== 16 | Final users: 17 | - breanna 18 | - elon 19 | ==================================== 20 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/6-clousures_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Can a closure mutate a variable outside its body?", 3 | "answers": [ 4 | "Yes", 5 | "No" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/6a-clousures_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "When a variable is enclosed in a closure, the enclosing function has access to ____", 3 | "answers": [ 4 | "a mutable reference to the original value", 5 | "a copy of the value" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/10-advanced_functions/exercises/7-anonymous_functions/expected.txt: -------------------------------------------------------------------------------- 1 | Message: "Here's Johnny!" Cost: 28 cents 2 | Message: "Go ahead, make my day" Cost: 42 cents 3 | Message: "You had me at hello" Cost: 38 cents 4 | Message: "There's no place like home" Cost: 52 cents 5 | ==================================== 6 | Message: "Hello, my name is Inigo Montoya. You killed my father. Prepare to die." Cost: 140 cents 7 | Message: "May the Force be with you." Cost: 52 cents 8 | Message: "Show me the money!" Cost: 36 cents 9 | Message: "Go ahead, make my day." Cost: 44 cents 10 | ==================================== 11 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/1-pointers_intro/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Message struct { 6 | Recipient string 7 | Text string 8 | } 9 | 10 | // Don't touch above this line 11 | 12 | func sendMessage(m Message) { 13 | fmt.Printf("To: %v\n", &m.Recipient) 14 | fmt.Printf("Message: %v\n", &m.Text) 15 | } 16 | 17 | // Don't touch below this line 18 | 19 | func test(recipient string, text string) { 20 | m := Message{Recipient: recipient, Text: text} 21 | sendMessage(m) 22 | fmt.Println("=====================================") 23 | } 24 | 25 | func main() { 26 | test("Lane", "Textio is getting better everyday!") 27 | test("Allan", "This pointer stuff is weird...") 28 | test("Tiffany", "What time will you be home for dinner?") 29 | } 30 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/1-pointers_intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Message struct { 6 | Recipient string 7 | Text string 8 | } 9 | 10 | // Don't touch above this line 11 | 12 | func sendMessage(m Message) { 13 | fmt.Printf("To: %v\n", m.Recipient) 14 | fmt.Printf("Message: %v\n", m.Text) 15 | } 16 | 17 | // Don't touch below this line 18 | 19 | func test(recipient string, text string) { 20 | m := Message{Recipient: recipient, Text: text} 21 | sendMessage(m) 22 | fmt.Println("=====================================") 23 | } 24 | 25 | func main() { 26 | test("Lane", "Textio is getting better everyday!") 27 | test("Allan", "This pointer stuff is weird...") 28 | test("Tiffany", "What time will you be home for dinner?") 29 | } 30 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/1-pointers_intro/expected.txt: -------------------------------------------------------------------------------- 1 | To: Lane 2 | Message: Textio is getting better everyday! 3 | ===================================== 4 | To: Allan 5 | Message: This pointer stuff is weird... 6 | ===================================== 7 | To: Tiffany 8 | Message: What time will you be home for dinner? 9 | ===================================== 10 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/2-pointers_practice/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func removeProfanity(message *string) { 8 | // ? 9 | } 10 | 11 | // don't touch below this line 12 | 13 | func test(messages []string) { 14 | for _, message := range messages { 15 | removeProfanity(&message) 16 | fmt.Println(message) 17 | } 18 | } 19 | 20 | func main() { 21 | messages1 := []string{ 22 | "well shoot, this is awful", 23 | "dang robots", 24 | "dang them to heck", 25 | } 26 | 27 | messages2 := []string{ 28 | "well shoot", 29 | "Allan is going straight to heck", 30 | "dang... that's a tough break", 31 | } 32 | 33 | test(messages1) 34 | test(messages2) 35 | } 36 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/2-pointers_practice/expected.txt: -------------------------------------------------------------------------------- 1 | well *****, this is awful 2 | **** robots 3 | **** them to **** 4 | well ***** 5 | Allan is going straight to **** 6 | ****... that's a tough break 7 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/3-pointers_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is the value of *y after the code on the left executes?", 3 | "answers": [ 4 | "100", 5 | "a memory address pointing to x", 6 | "nil", 7 | "50" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/3-pointers_quiz/readme.md: -------------------------------------------------------------------------------- 1 | # Pointers Quiz 2 | 3 | ```go 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | var x int = 50 10 | var y *int = &x 11 | *y = 100 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/3b-pointers_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is the value of x after the code on the left executes?", 3 | "answers": [ 4 | "100", 5 | "a memory address pointing to x", 6 | "50", 7 | "nil" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/3b-pointers_quiz/readme.md: -------------------------------------------------------------------------------- 1 | # Pointers Quiz 2 | 3 | ```go 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | var x int = 50 10 | var y *int = &x 11 | *y = 100 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/4-nil_dereference/expected.txt: -------------------------------------------------------------------------------- 1 | well *****, this is awful 2 | nil message detected 3 | **** robots 4 | **** them to **** 5 | nil message detected 6 | well ***** 7 | nil message detected 8 | Allan is going straight to **** 9 | ****... that's a tough break 10 | nil message detected 11 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/4-nil_dereference/readme.md: -------------------------------------------------------------------------------- 1 | # Nil Pointers 2 | 3 | Pointers can be very dangerous. 4 | 5 | If a pointer points to nothing (the zero value of the pointer type) then dereferencing it will cause a runtime error (a [panic](https://gobyexample.com/panic)) that crashes the program. Generally speaking, whenever you're dealing with pointers you should check if it's `nil` before trying to dereference it. 6 | 7 | ## Assignment 8 | 9 | Let's make our profanity checker *safe*. Update the `removeProfanity` function. If `message` is `nil`, `return` early to avoid a [panic](https://gobyexample.com/panic). After all, there are no bad words to remove. 10 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/5-pointer_receiver/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which is more widely used in Go?", 3 | "answers": [ 4 | "Pointer receivers", 5 | "Value receivers" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/11-pointers/exercises/6-pointer_receiver_code/expected.txt: -------------------------------------------------------------------------------- 1 | -- before -- 2 | message: this is my first draft 3 | fromAddress: sandra@mailio-test.com 4 | toAddress: bullock@mailio-test.com 5 | -- end before -- 6 | -- after -- 7 | message: this is my second draft 8 | fromAddress: sandra@mailio-test.com 9 | toAddress: bullock@mailio-test.com 10 | -- end after -- 11 | ========================== 12 | -- before -- 13 | message: this is my third draft 14 | fromAddress: sandra@mailio-test.com 15 | toAddress: bullock@mailio-test.com 16 | -- end before -- 17 | -- after -- 18 | message: this is my second draft 19 | fromAddress: sandra@mailio-test.com 20 | toAddress: bullock@mailio-test.com 21 | -- end after -- 22 | ========================== 23 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/1-intro/code.go: -------------------------------------------------------------------------------- 1 | package mailio 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func test(text string) { 8 | fmt.Println(text) 9 | } 10 | 11 | func main() { 12 | test("starting Mailio server") 13 | test("stopping Mailio server") 14 | } 15 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/1-intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func test(text string) { 8 | fmt.Println(text) 9 | } 10 | 11 | func main() { 12 | test("starting Mailio server") 13 | test("stopping Mailio server") 14 | } 15 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/1-intro/expected.txt: -------------------------------------------------------------------------------- 1 | starting Mailio server 2 | stopping Mailio server 3 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/10-go_install/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does 'go install' do?", 3 | "answers": [ 4 | "Compiles and installs the program locally", 5 | "Installs dependencies", 6 | "Saves local code to the remote source control provider" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/10-go_install/readme.md: -------------------------------------------------------------------------------- 1 | # Go Install 2 | 3 | ## Build an executable 4 | 5 | Ensure you are in your `hellogo` repo, then run: 6 | 7 | ```bash 8 | go install 9 | ``` 10 | 11 | Navigate out of your project directory: 12 | 13 | ```bash 14 | cd ../ 15 | ``` 16 | 17 | Go has installed the `hellogo` program globally. Run it with: 18 | 19 | ```bash 20 | hellogo 21 | ``` 22 | 23 | ## Tip about "not found" 24 | 25 | If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, `go install` is adding your binary to your `GOBIN` directory, but that may not be in your `PATH`. 26 | 27 | You can read more about that here in the [go install docs](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies). 28 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/10a-go_install/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Code must be compiled with 'go build' before running 'go install'", 3 | "answers": [ 4 | "False", 5 | "True" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/10a-go_install/readme.md: -------------------------------------------------------------------------------- 1 | # Go Install 2 | 3 | ## Build an executable 4 | 5 | Ensure you are in your `hellogo` repo, then run: 6 | 7 | ```bash 8 | go install 9 | ``` 10 | 11 | Navigate out of your project directory: 12 | 13 | ```bash 14 | cd ../ 15 | ``` 16 | 17 | Go has installed the `hellogo` program globally. Run it with: 18 | 19 | ```bash 20 | hellogo 21 | ``` 22 | 23 | ## Tip about "not found" 24 | 25 | If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, `go install` is adding your binary to your `GOBIN` directory, but that may not be in your `PATH`. 26 | 27 | You can read more about that here in the [go install docs](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies). 28 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/11-custom_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What was the output from 'go build' in the library package", 3 | "answers": [ 4 | "The compiled package is silently saved to the local build cache", 5 | "An executable program" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/11a-custom_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why is the function 'Reverse()' instead of 'reverse()'?", 3 | "answers": [ 4 | "Lowercase names aren't exported for external use", 5 | "Conventionally uppercase names are used in Go" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/11b-custom_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Does a package in a folder named 'dateparser' need to also be called 'dateparser'", 3 | "answers": [ 4 | "No, but it should by convention", 5 | "Yes", 6 | "No, by convention we should avoid consistent naming" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/12-custom_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What was printed by the new hellogo program?", 3 | "answers": [ 4 | "dlrow olleh", 5 | "hello world", 6 | "world hello" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/12a-custom_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How does the go toolchain know where to find the imported code?", 3 | "answers": [ 4 | "We used the 'replace' keyword in go.mod to point it to the relative location of mystrings", 5 | "It downloads it from Google's servers", 6 | "NPM hosts the files publicly", 7 | "It was fetched from Github" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/13-remote_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How did the Go toolchain know where to download the go-tinytime package?", 3 | "answers": [ 4 | "The module import path is used for remote lookups, e.g. https://github.com/wagslane/go-tinytime", 5 | "The go toolchain has every open-source Go module's location memorized" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/13a-remote_package/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What was printed after running the new datetest program?", 3 | "answers": [ 4 | "2020-04-03T14:12:54Z", 5 | "2025-04-03T14:12:54Z", 6 | "Year: 2020, Month: 04, Day: 05" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/14-library_packages_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Should you export code from the main package?", 3 | "answers": [ 4 | "Nope", 5 | "Yup" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/14a-library_packages_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "When should you NOT export a function, variable, or type?", 3 | "answers": [ 4 | "When the end-user doesn't need to know about it", 5 | "Never, its better to share code!" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/14b-library_packages_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Should you often change a package's exported API?", 3 | "answers": [ 4 | "No, try to keep changes to internal functionality", 5 | "Yes, move fast and break things", 6 | "If the package is 'main' then yes" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/2-package_naming/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What would be the conventional package name of a package with the path github.com/wagslane/parser?", 3 | "answers": [ 4 | "parser", 5 | "wagslane", 6 | "go-parser", 7 | "github.com" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/2a-package_naming/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Given the import path of path/to/rand, which of these is a valid package name?", 3 | "answers": [ 4 | "Any of these", 5 | "path", 6 | "rand", 7 | "random", 8 | "spam" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/3-help/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the 'go version' command print?", 3 | "answers": [ 4 | "go version {version} {os}/{architecture}", 5 | "go version {os}/{architecture} {version}", 6 | "{version} {os}/{architecture}" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/4-modules/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is a Go module?", 3 | "answers": [ 4 | "A collection of packages that are released together", 5 | "A library package", 6 | "An executable main package", 7 | "A file of Go code" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/4a-modules/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Do packages in the standard library have a module path prefix?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/4b-modules/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is an import path?", 3 | "answers": [ 4 | "A module path + package subdirectory", 5 | "An HTTP connection", 6 | "A RESTful server" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/5-gopath/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Do you need to put your code inside of your GOPATH?", 3 | "answers": [ 4 | "No, in fact you shouldn't", 5 | "It doesn't matter", 6 | "Yes" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6-first_program/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why does Go include a remote URL in module paths?", 3 | "answers": [ 4 | "To simplify remote downloading of packages", 5 | "To confuse new gophers", 6 | "To ensure that developers are using source control" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6-first_program/readme.md: -------------------------------------------------------------------------------- 1 | # First Local Program 2 | 3 | Once inside your personal workspace, create a new directory and enter it: 4 | 5 | ```bash 6 | mkdir hellogo 7 | cd hellogo 8 | ``` 9 | 10 | Inside the directory declare your module's name: 11 | 12 | ```bash 13 | go mod init {REMOTE}/{USERNAME}/hellogo 14 | ``` 15 | 16 | Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo` 17 | 18 | Print your `go.mod` file: 19 | 20 | ```bash 21 | cat go.mod 22 | ``` 23 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6a-first_program/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is hellogo in our case?", 3 | "answers": [ 4 | "The repository/directory name", 5 | "The module path prefix" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6a-first_program/readme.md: -------------------------------------------------------------------------------- 1 | # First Local Program 2 | 3 | Once inside your personal workspace, create a new directory and enter it: 4 | 5 | ```bash 6 | mkdir hellogo 7 | cd hellogo 8 | ``` 9 | 10 | Inside the directory declare your module's name: 11 | 12 | ```bash 13 | go mod init {REMOTE}/{USERNAME}/hellogo 14 | ``` 15 | 16 | Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo` 17 | 18 | Print your `go.mod` file: 19 | 20 | ```bash 21 | cat go.mod 22 | ``` 23 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6b-first_program/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the first line of go.mod contain?", 3 | "answers": [ 4 | "module {REMOTE}/{USERNAME}/hellogo", 5 | "{REMOTE}/{USERNAME}/hellogo", 6 | "module hellogo" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/6b-first_program/readme.md: -------------------------------------------------------------------------------- 1 | # First Local Program 2 | 3 | Once inside your personal workspace, create a new directory and enter it: 4 | 5 | ```bash 6 | mkdir hellogo 7 | cd hellogo 8 | ``` 9 | 10 | Inside the directory declare your module's name: 11 | 12 | ```bash 13 | go mod init {REMOTE}/{USERNAME}/hellogo 14 | ``` 15 | 16 | Where `{REMOTE}` is your preferred remote source provider (i.e. `github.com`) and `{USERNAME}` is your Git username. If you don't use a remote provider yet, just use `example.com/username/hellogo` 17 | 18 | Print your `go.mod` file: 19 | 20 | ```bash 21 | cat go.mod 22 | ``` 23 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/7-go_run/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Does 'go run' build a production executable?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/7a-go_run/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which can 'go run' accept as arguments?", 3 | "answers": [ 4 | "Both", 5 | "File names", 6 | "Package names" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/8-go_build/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What was created after running 'go build'?", 3 | "answers": [ 4 | "An executable file named 'hellogo'", 5 | "An executable file named main", 6 | "A package named cmd" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/8-go_build/readme.md: -------------------------------------------------------------------------------- 1 | # Go Build 2 | 3 | `go build` compiles go code into an executable program 4 | 5 | ## Build an executable 6 | 7 | Ensure you are in your hellogo repo, then run: 8 | 9 | ```bash 10 | go build 11 | ``` 12 | 13 | Run the new program: 14 | 15 | ```bash 16 | ./hellogo 17 | ``` 18 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/8a-go_build/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What happens when you run './hellogo'?", 3 | "answers": [ 4 | "'hello world' is printed", 5 | "The program panics", 6 | "The code compiles" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/12-local_development/exercises/8a-go_build/readme.md: -------------------------------------------------------------------------------- 1 | # Go Build 2 | 3 | `go build` compiles go code into an executable program 4 | 5 | ## Build an executable 6 | 7 | Ensure you are in your hellogo repo, then run: 8 | 9 | ```bash 10 | go build 11 | ``` 12 | 13 | Run the new program: 14 | 15 | ```bash 16 | ./hellogo 17 | ``` 18 | -------------------------------------------------------------------------------- /course/13-channels/challenges/1-channels_practice/expected.txt: -------------------------------------------------------------------------------- 1 | Starting game... 2 | ping 0 sent 3 | ping 0 got pong 0 sent 4 | pong 0 got 5 | ping 1 sent 6 | ping 1 got pong 1 sent 7 | pong 1 got 8 | ping 2 sent 9 | ping 2 got pong 2 sent 10 | pong 2 got 11 | ping 3 sent 12 | ping 3 got pong 3 sent 13 | pong 3 got 14 | pings done 15 | pongs done 16 | ===== Game over ===== 17 | Starting game... 18 | ping 0 sent 19 | ping 0 got pong 0 sent 20 | pong 0 got 21 | ping 1 sent 22 | ping 1 got pong 1 sent 23 | pong 1 got 24 | ping 2 sent 25 | ping 2 got pong 2 sent 26 | pong 2 got 27 | pings done 28 | pongs done 29 | ===== Game over ===== 30 | Starting game... 31 | ping 0 sent 32 | ping 0 got pong 0 sent 33 | pong 0 got 34 | ping 1 sent 35 | ping 1 got pong 1 sent 36 | pong 1 got 37 | pings done 38 | pongs done 39 | ===== Game over ===== 40 | -------------------------------------------------------------------------------- /course/13-channels/challenges/1-channels_practice/readme.md: -------------------------------------------------------------------------------- 1 | # Ping Pong 2 | 3 | Many tech companies play ping pong in the office during break time. At Mailio, we play virtual ping pong using channels. 4 | 5 | ## Assignment 6 | 7 | Fix the bug in the `pingPong` function. It shouldn't `return` until the entire game of ping pong is complete. 8 | -------------------------------------------------------------------------------- /course/13-channels/exercises/1-intro/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func sendEmail(message string) { 9 | func() { 10 | time.Sleep(time.Millisecond * 250) 11 | fmt.Printf("Email received: '%s'\n", message) 12 | }() 13 | fmt.Printf("Email sent: '%s'\n", message) 14 | } 15 | 16 | // Don't touch below this line 17 | 18 | func test(message string) { 19 | sendEmail(message) 20 | time.Sleep(time.Millisecond * 500) 21 | fmt.Println("========================") 22 | } 23 | 24 | func main() { 25 | test("Hello there Stacy!") 26 | test("Hi there John!") 27 | test("Hey there Jane!") 28 | } 29 | -------------------------------------------------------------------------------- /course/13-channels/exercises/1-intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func sendEmail(message string) { 9 | go func() { 10 | time.Sleep(time.Millisecond * 250) 11 | fmt.Printf("Email received: '%s'\n", message) 12 | }() 13 | fmt.Printf("Email sent: '%s'\n", message) 14 | } 15 | 16 | // Don't touch below this line 17 | 18 | func test(message string) { 19 | sendEmail(message) 20 | time.Sleep(time.Millisecond * 500) 21 | fmt.Println("========================") 22 | } 23 | 24 | func main() { 25 | test("Hello there Stacy!") 26 | test("Hi there John!") 27 | test("Hey there Jane!") 28 | } 29 | -------------------------------------------------------------------------------- /course/13-channels/exercises/1-intro/expected.txt: -------------------------------------------------------------------------------- 1 | Email sent: 'Hello there Stacy!' 2 | Email received: 'Hello there Stacy!' 3 | ======================== 4 | Email sent: 'Hi there John!' 5 | Email received: 'Hi there John!' 6 | ======================== 7 | Email sent: 'Hey there Jane!' 8 | Email received: 'Hey there Jane!' 9 | ======================== 10 | -------------------------------------------------------------------------------- /course/13-channels/exercises/2-channels_deadlock/expected.txt: -------------------------------------------------------------------------------- 1 | email 1 is old: true 2 | email 2 is old: false 3 | email 3 is old: false 4 | ========================================== 5 | email 1 is old: true 6 | email 2 is old: true 7 | email 3 is old: true 8 | ========================================== 9 | email 1 is old: true 10 | email 2 is old: false 11 | email 3 is old: false 12 | ========================================== 13 | email 1 is old: true 14 | email 2 is old: false 15 | email 3 is old: false 16 | ========================================== 17 | -------------------------------------------------------------------------------- /course/13-channels/exercises/3-channels_send/expected.txt: -------------------------------------------------------------------------------- 1 | Waiting for 3 databases... 2 | Database 1 is online 3 | Database 2 is online 4 | Database 3 is online 5 | All databases are online! 6 | ===================================== 7 | Waiting for 4 databases... 8 | Database 1 is online 9 | Database 2 is online 10 | Database 3 is online 11 | Database 4 is online 12 | All databases are online! 13 | ===================================== 14 | Waiting for 5 databases... 15 | Database 1 is online 16 | Database 2 is online 17 | Database 3 is online 18 | Database 4 is online 19 | Database 5 is online 20 | All databases are online! 21 | ===================================== 22 | -------------------------------------------------------------------------------- /course/13-channels/exercises/4-buffered_channels/expected.txt: -------------------------------------------------------------------------------- 1 | Adding 2 emails to queue... 2 | Sending emails... 3 | Sending email: Hello John, tell Kathy I said hi 4 | Sending email: Whazzup bruther 5 | ========================================== 6 | Adding 3 emails to queue... 7 | Sending emails... 8 | Sending email: I find that hard to believe. 9 | Sending email: When? I don't know if I can 10 | Sending email: What time are you thinking? 11 | ========================================== 12 | Adding 4 emails to queue... 13 | Sending emails... 14 | Sending email: She says hi! 15 | Sending email: Yeah its tomorrow. So we're good. 16 | Sending email: Cool see you then! 17 | Sending email: Bye! 18 | ========================================== 19 | -------------------------------------------------------------------------------- /course/13-channels/exercises/5-close/expected.txt: -------------------------------------------------------------------------------- 1 | Start counting... 2 | Sent batch of 15 reports 3 | Sent batch of 38 reports 4 | Sent batch of 61 reports 5 | 114 reports sent! 6 | ======================== 7 | Start counting... 8 | Sent batch of 15 reports 9 | Sent batch of 38 reports 10 | Sent batch of 61 reports 11 | Sent batch of 84 reports 12 | 198 reports sent! 13 | ======================== 14 | Start counting... 15 | Sent batch of 15 reports 16 | Sent batch of 38 reports 17 | Sent batch of 61 reports 18 | Sent batch of 84 reports 19 | Sent batch of 107 reports 20 | 305 reports sent! 21 | ======================== 22 | Start counting... 23 | Sent batch of 15 reports 24 | Sent batch of 38 reports 25 | Sent batch of 61 reports 26 | Sent batch of 84 reports 27 | Sent batch of 107 reports 28 | Sent batch of 130 reports 29 | 435 reports sent! 30 | ======================== 31 | -------------------------------------------------------------------------------- /course/13-channels/exercises/6-range/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func concurrrentFib(n int) { 9 | // ? 10 | } 11 | 12 | // TEST SUITE - Don't touch below this line 13 | 14 | func test(n int) { 15 | fmt.Printf("Printing %v numbers...\n", n) 16 | concurrrentFib(n) 17 | fmt.Println("==============================") 18 | } 19 | 20 | func main() { 21 | test(10) 22 | test(5) 23 | test(20) 24 | test(13) 25 | } 26 | 27 | func fibonacci(n int, ch chan int) { 28 | x, y := 0, 1 29 | for i := 0; i < n; i++ { 30 | ch <- x 31 | x, y = y, x+y 32 | time.Sleep(time.Millisecond * 10) 33 | } 34 | close(ch) 35 | } 36 | -------------------------------------------------------------------------------- /course/13-channels/exercises/6-range/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func concurrrentFib(n int) { 9 | ch := make(chan int) 10 | go fibonacci(n, ch) 11 | 12 | for v := range ch { 13 | fmt.Println(v) 14 | } 15 | } 16 | 17 | // TEST SUITE - Don't touch below this line 18 | 19 | func test(n int) { 20 | fmt.Printf("Printing %v numbers...\n", n) 21 | concurrrentFib(n) 22 | fmt.Println("==============================") 23 | } 24 | 25 | func main() { 26 | test(10) 27 | test(5) 28 | test(20) 29 | test(13) 30 | } 31 | 32 | func fibonacci(n int, ch chan int) { 33 | x, y := 0, 1 34 | for i := 0; i < n; i++ { 35 | ch <- x 36 | x, y = y, x+y 37 | time.Sleep(time.Millisecond * 10) 38 | } 39 | close(ch) 40 | } 41 | -------------------------------------------------------------------------------- /course/13-channels/exercises/6-range/expected.txt: -------------------------------------------------------------------------------- 1 | Printing 10 numbers... 2 | 0 3 | 1 4 | 1 5 | 2 6 | 3 7 | 5 8 | 8 9 | 13 10 | 21 11 | 34 12 | ============================== 13 | Printing 5 numbers... 14 | 0 15 | 1 16 | 1 17 | 2 18 | 3 19 | ============================== 20 | Printing 20 numbers... 21 | 0 22 | 1 23 | 1 24 | 2 25 | 3 26 | 5 27 | 8 28 | 13 29 | 21 30 | 34 31 | 55 32 | 89 33 | 144 34 | 233 35 | 377 36 | 610 37 | 987 38 | 1597 39 | 2584 40 | 4181 41 | ============================== 42 | Printing 13 numbers... 43 | 0 44 | 1 45 | 1 46 | 2 47 | 3 48 | 5 49 | 8 50 | 13 51 | 21 52 | 34 53 | 55 54 | 89 55 | 144 56 | ============================== 57 | -------------------------------------------------------------------------------- /course/13-channels/exercises/7-select/expected.txt: -------------------------------------------------------------------------------- 1 | Starting... 2 | SMS: hi friend 3 | Email: Will you make your appointment? 4 | SMS: What's going on? 5 | Email: Let's be friends 6 | Email: What are you doing? 7 | SMS: Welcome to the business 8 | Email: I can't believe you've done this. 9 | SMS: I'll pay you to be my friend 10 | =============================== 11 | Starting... 12 | SMS: this song slaps hard 13 | Email: What do you think of this song? 14 | Email: I hate this band 15 | SMS: yooo hoooo 16 | SMS: i'm a big fan 17 | Email: Can you believe this song? 18 | =============================== 19 | -------------------------------------------------------------------------------- /course/13-channels/exercises/8-select_default/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func saveBackups(snapshotTicker, saveAfter <-chan time.Time) { 9 | // ? 10 | } 11 | 12 | // TEST SUITE - Don't touch below this line 13 | 14 | func takeSnapshot() { 15 | fmt.Println("Taking a backup snapshot...") 16 | } 17 | 18 | func saveSnapshot() { 19 | fmt.Println("All backups saved!") 20 | } 21 | 22 | func waitForData() { 23 | fmt.Println("Nothing to do, waiting...") 24 | } 25 | 26 | func test() { 27 | snapshotTicker := time.Tick(800 * time.Millisecond) 28 | saveAfter := time.After(2800 * time.Millisecond) 29 | saveBackups(snapshotTicker, saveAfter) 30 | fmt.Println("===========================") 31 | } 32 | 33 | func main() { 34 | test() 35 | } 36 | -------------------------------------------------------------------------------- /course/13-channels/exercises/8-select_default/expected.txt: -------------------------------------------------------------------------------- 1 | Nothing to do, waiting... 2 | Nothing to do, waiting... 3 | Taking a backup snapshot... 4 | Nothing to do, waiting... 5 | Nothing to do, waiting... 6 | Taking a backup snapshot... 7 | Nothing to do, waiting... 8 | Taking a backup snapshot... 9 | Nothing to do, waiting... 10 | All backups saved! 11 | =========================== 12 | -------------------------------------------------------------------------------- /course/13-channels/exercises/9-channel_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What happens when you read from a nil channel?", 3 | "answers": [ 4 | "The receiver will block forever", 5 | "The sender will block forever", 6 | "Panic" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/13-channels/exercises/9a-channel_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What happens when you send to a closed channel?", 3 | "answers": [ 4 | "Panic", 5 | "The sender will block forever", 6 | "The receiver will block forever" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/1-mutex/expected.txt: -------------------------------------------------------------------------------- 1 | Email: jill@example.com has 98 emails 2 | Email: john@example.com has 52 emails 3 | ===================================== 4 | Email: george@example.com has 579 emails 5 | Email: kaden@example.com has 54 emails 6 | ===================================== 7 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/3-mutex_name/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the term 'mutex' refer to?", 3 | "answers": [ 4 | "Mutual Exclusion", 5 | "Most Expressive", 6 | "More Expansive", 7 | "Mutual Expulsion" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/3-mutex_name/readme.md: -------------------------------------------------------------------------------- 1 | # Why is it called a "mutex"? 2 | 3 | Mutex is short for [mutual exclusion](https://en.wikipedia.org/wiki/Mutual_exclusion), and the conventional name for the data structure that provides it is "mutex", often abbreviated to "mux". 4 | 5 | It's called "mutual exclusion" because a mutex *excludes* different threads (or goroutines) from accessing the same data at the same time. 6 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/4-mutex_review/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How many threads can Lock a mutex at once?", 3 | "answers": [ 4 | "1", 5 | "4", 6 | "0", 7 | "infinite" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/4a-mutex_review/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why would you use a mutex?", 3 | "answers": [ 4 | "To safely access a data structure concurrently", 5 | "To protect data from network access", 6 | "To stop other packages from using my code", 7 | "To encapsulate private data members of a struct" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/5-rw_mutex/expected.txt: -------------------------------------------------------------------------------- 1 | Email: jill@example.com has 98 emails 2 | Email: john@example.com has 52 emails 3 | ===================================== 4 | Email: george@example.com has 579 emails 5 | Email: kaden@example.com has 54 emails 6 | ===================================== 7 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/6-rw_mutex_review/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How many writers can access a RWMutex at once?", 3 | "answers": [ 4 | "1", 5 | "0", 6 | "infinite" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/6a-rw_mutex_review/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How many readers can access a RWMutex at once?", 3 | "answers": [ 4 | "infinite", 5 | "1", 6 | "0" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/14-mutexes/exercises/6b-rw_mutex_review/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Can readers and writers use RWMutexes at the same time?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/15-generics/exercises/1-generics/expected.txt: -------------------------------------------------------------------------------- 1 | Getting last email from slice of length: 0 2 | Last item in list: { } 3 | --- 4 | Getting last email from slice of length: 3 5 | Item #1: {Hi Margo janet@example.com margo@example.com} 6 | Item #2: {Hey Margo I really wanna chat janet@example.com margo@example.com} 7 | Item #3: {ANSWER ME janet@example.com margo@example.com} 8 | Last item in list: {ANSWER ME janet@example.com margo@example.com} 9 | --- 10 | Getting last payment from slice of length: 4 11 | Item #1: {5 jane@example.com sally@example.com} 12 | Item #2: {25 jane@example.com mark@example.com} 13 | Item #3: {1 jane@example.com sally@example.com} 14 | Item #4: {16 jane@example.com margo@example.com} 15 | Last item in list: {16 jane@example.com margo@example.com} 16 | --- 17 | -------------------------------------------------------------------------------- /course/15-generics/exercises/2-generics_why/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which code would generics be most likely to help with?", 3 | "answers": [ 4 | "A binary tree", 5 | "Calculating the area of a circle", 6 | "Detecting whether or not a string contains a given substring" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/15-generics/exercises/2a-generics_why/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Go's approach to language design is...", 3 | "answers": [ 4 | "Resist adding new features unless they're extremely important", 5 | "To support as many useful features as possible", 6 | "To never add new features, the language doesn't change" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/15-generics/exercises/2b-generics_why/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Generics will probably be used more heavily in...", 3 | "answers": [ 4 | "Library packages", 5 | "Main packages (executable applications)" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/15-generics/exercises/3-constraints/expected.txt: -------------------------------------------------------------------------------- 1 | --- 2 | Charging customer for a 'yearly subscription', current balance is 1000... 3 | New balance is: 750. Total number of line items is now 1 4 | --- 5 | Charging customer for a 'monthly subscription', current balance is 686.2... 6 | New balance is: 661.2. Total number of line items is now 3 7 | --- 8 | Charging customer for a 'one time usage plan with 5000 emails', current balance is 756.2... 9 | New balance is: 606.2. Total number of line items is now 1 10 | --- 11 | Charging customer for a 'one time usage plan with 100000 emails', current balance is 32.2... 12 | Got error: insufficient funds 13 | -------------------------------------------------------------------------------- /course/15-generics/exercises/4-interface_type_lists/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why might you create an interface using a type list?", 3 | "answers": [ 4 | "You know exactly which types satisfy your interface", 5 | "It's too much trouble to define the methods required by your interface" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/15-generics/exercises/4-interface_type_lists/readme.md: -------------------------------------------------------------------------------- 1 | # Interface type lists 2 | 3 | When generics were released, a new way of writing interfaces was also released at the same time! 4 | 5 | We can now simply list a bunch of types to get a new interface/constraint. 6 | 7 | ```go 8 | // Ordered is a type constraint that matches any ordered type. 9 | // An ordered type is one that supports the <, <=, >, and >= operators. 10 | type Ordered interface { 11 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 12 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | 13 | ~float32 | ~float64 | 14 | ~string 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /course/15-generics/exercises/5-parametric_constraints/expected.txt: -------------------------------------------------------------------------------- 1 | Using 'basic user biller' to create a bill for 'joe@example.com' 2 | Bill created for 50 dollars 3 | --- 4 | Using 'basic user biller' to create a bill for 'samuel.boggs@example.com' 5 | Bill created for 50 dollars 6 | --- 7 | Using 'pro user biller' to create a bill for 'jade.row@example.com' 8 | Bill created for 100 dollars 9 | --- 10 | Using 'basic org biller' to create a bill for 'challis.rane@example.com' 11 | Bill created for 2000 dollars 12 | --- 13 | Using 'pro org biller' to create a bill for 'challis.rane@example.com' 14 | Bill created for 3000 dollars 15 | --- 16 | -------------------------------------------------------------------------------- /course/15-generics/exercises/6-type_names/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "The name of a type parameter...", 3 | "answers": [ 4 | "...can be anything, but 'T' is a common convention", 5 | "...can and should be whatever you want", 6 | "...must be 'T'" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/15-generics/exercises/6-type_names/readme.md: -------------------------------------------------------------------------------- 1 | # Naming Generic Types 2 | 3 | Let's look at this simple example again: 4 | 5 | ```go 6 | func splitAnySlice[T any](s []T) ([]T, []T) { 7 | mid := len(s)/2 8 | return s[:mid], s[mid:] 9 | } 10 | ``` 11 | 12 | Remember, `T` is just a variable name, We could have named the type parameter *anything*. `T` happens to be a fairly common convention for a type variable, similar to how `i` is a convention for index variables in loops. 13 | 14 | This is just as valid: 15 | 16 | ```go 17 | func splitAnySlice[MyAnyType any](s []MyAnyType) ([]MyAnyType, []MyAnyType) { 18 | mid := len(s)/2 19 | return s[:mid], s[mid:] 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /course/16-go_facts/exercises/1-proverbs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which is better?", 3 | "answers": [ 4 | "Clear", 5 | "Clever" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/16-go_facts/exercises/1a-proverbs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which is better?", 3 | "answers": [ 4 | "Copying a little code", 5 | "Including a little dependency" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/16-go_facts/exercises/1b-proverbs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Documentation should primarily be written for...", 3 | "answers": [ 4 | "Users of your code", 5 | "Maintainers of your code", 6 | "Yourself" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/16-go_facts/exercises/1c-proverbs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Errors are...", 3 | "answers": [ 4 | "Just values", 5 | "Special", 6 | "Exceptions", 7 | "Something to be caught" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/2-variables/exercises/1-basic_types/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // initialize variables here 7 | 8 | fmt.Printf("%v %f %v %q\n", smsSendingLimit, costPerSMS, hasPermission, username) 9 | } 10 | -------------------------------------------------------------------------------- /course/2-variables/exercises/1-basic_types/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var smsSendingLimit int 7 | var costPerSMS float64 8 | var hasPermission bool 9 | var username string 10 | fmt.Printf("%v %f %v %q\n", smsSendingLimit, costPerSMS, hasPermission, username) 11 | } 12 | -------------------------------------------------------------------------------- /course/2-variables/exercises/1-basic_types/expected.txt: -------------------------------------------------------------------------------- 1 | 0 0.000000 false "" 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/10-conditionals/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | messageLen := 10 7 | maxMessageLen := 20 8 | fmt.Println("Trying to send a message of length:", messageLen, "and a max length of:", maxMessageLen) 9 | 10 | // don't touch above this line 11 | 12 | if messageLen > maxMessageLen { 13 | fmt.Println("Message sent") 14 | } else { 15 | fmt.Println("Message not sent") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /course/2-variables/exercises/10-conditionals/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | messageLen := 10 7 | maxMessageLen := 20 8 | fmt.Println("Trying to send a message of length:", messageLen, "and a max length of:", maxMessageLen) 9 | 10 | // don't touch above this line 11 | 12 | if messageLen <= maxMessageLen { 13 | fmt.Println("Message sent") 14 | } else { 15 | fmt.Println("Message not sent") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /course/2-variables/exercises/10-conditionals/expected.txt: -------------------------------------------------------------------------------- 1 | Trying to send a message of length: 10 and a max length of: 20 2 | Message sent 3 | -------------------------------------------------------------------------------- /course/2-variables/exercises/11-if_init/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why would you use the 'initial' section of an `if` statement?", 3 | "answers": [ 4 | "To keep the code concise and the scope limited", 5 | "To speed up my code", 6 | "To confuse other programmers" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/2-variables/exercises/2-short_declarations/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // declare here 7 | 8 | fmt.Println(congrats) 9 | } 10 | -------------------------------------------------------------------------------- /course/2-variables/exercises/2-short_declarations/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // declare here 7 | congrats := "happy birthday!" 8 | 9 | fmt.Println(congrats) 10 | } 11 | -------------------------------------------------------------------------------- /course/2-variables/exercises/2-short_declarations/expected.txt: -------------------------------------------------------------------------------- 1 | happy birthday! 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/3-type_inference/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | penniesPerText := 2 7 | fmt.Printf("The type of penniesPerText is %T\n", penniesPerText) 8 | } 9 | -------------------------------------------------------------------------------- /course/2-variables/exercises/3-type_inference/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | penniesPerText := 0.02 7 | fmt.Printf("The type of penniesPerText is %T\n", penniesPerText) 8 | } 9 | -------------------------------------------------------------------------------- /course/2-variables/exercises/3-type_inference/expected.txt: -------------------------------------------------------------------------------- 1 | The type of penniesPerText is float64 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/4-same_line_declarations/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // declare here 7 | 8 | fmt.Println(averageOpenRate, displayMessage) 9 | } 10 | -------------------------------------------------------------------------------- /course/2-variables/exercises/4-same_line_declarations/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // declare here 7 | averageOpenRate, displayMessage := .23, "is the average open rate of your messages" 8 | fmt.Println(averageOpenRate, displayMessage) 9 | } 10 | -------------------------------------------------------------------------------- /course/2-variables/exercises/4-same_line_declarations/expected.txt: -------------------------------------------------------------------------------- 1 | 0.23 is the average open rate of your messages 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/4-same_line_declarations/readme.md: -------------------------------------------------------------------------------- 1 | # Same Line Declarations 2 | 3 | We are able to declare multiple variables on the same line: 4 | 5 | ```go 6 | mileage, company := 80276, "Tesla" 7 | 8 | // is the same as 9 | 10 | mileage := 80276 11 | company := "Tesla" 12 | ``` 13 | ## Assignment 14 | 15 | Within the main function, declare a float called `averageOpenRate` and string called `displayMessage` on the same line. 16 | 17 | Initialize them to values of `.23` and `is the average open rate of your messages` respectively before they are printed. 18 | -------------------------------------------------------------------------------- /course/2-variables/exercises/5-type_sizes/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | accountAge := 2.6 7 | 8 | // create a new "accountAgeInt" here 9 | // it should be the result of casting "accountAge" to an integer 10 | 11 | fmt.Println("Your account has existed for", accountAgeInt, "years") 12 | } 13 | -------------------------------------------------------------------------------- /course/2-variables/exercises/5-type_sizes/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | accountAge := 2.6 7 | 8 | // create a new "accountAgeInt" here 9 | // it should be the result of casting "accountAge" to an integer 10 | accountAgeInt := int(accountAge) 11 | 12 | fmt.Println("Your account has existed for", accountAgeInt, "years") 13 | } 14 | -------------------------------------------------------------------------------- /course/2-variables/exercises/5-type_sizes/expected.txt: -------------------------------------------------------------------------------- 1 | Your account has existed for 2 years 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/6-which_type_to_use/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "When should you elect to NOT use a 'default type'?", 3 | "answers": [ 4 | "When performance and memory are the primary concern", 5 | "When my system has lots of extra hardware I want to utilize", 6 | "When either a 'default' or a specific size will work" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/2-variables/exercises/6a-which_type_to_use/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the size of a type indicate?", 3 | "answers": [ 4 | "Bits", 5 | "Bytes", 6 | "Nibbles" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/2-variables/exercises/7-constants/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const premiumPlanName = "Premium Plan" 7 | premiumPlanName = "Basic Plan" 8 | 9 | // don't edit below this line 10 | 11 | fmt.Println("plan:", premiumPlanName) 12 | fmt.Println("plan:", basicPlanName) 13 | } 14 | -------------------------------------------------------------------------------- /course/2-variables/exercises/7-constants/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const premiumPlanName = "Premium Plan" 7 | const basicPlanName = "Basic Plan" 8 | 9 | // don't edit below this line 10 | 11 | fmt.Println("plan:", premiumPlanName) 12 | fmt.Println("plan:", basicPlanName) 13 | } 14 | -------------------------------------------------------------------------------- /course/2-variables/exercises/7-constants/expected.txt: -------------------------------------------------------------------------------- 1 | plan: Premium Plan 2 | plan: Basic Plan 3 | -------------------------------------------------------------------------------- /course/2-variables/exercises/7-constants/readme.md: -------------------------------------------------------------------------------- 1 | # Constants 2 | 3 | Constants are declared like variables but use the `const` keyword. Constants can't use the `:=` short declaration syntax. 4 | 5 | Constants can be character, string, boolean, or numeric values. They *can not* be more complex types like slices, maps and structs, which are types we will explain later. 6 | 7 | As the name implies, the value of a constant can't be changed after it has been declared. 8 | 9 | ## Use two separate constants 10 | 11 | Something weird is happening in this code. 12 | 13 | What *should* be happening is that we create 2 separate constants: `premiumPlanName` and `basicPlanName`. Right now it looks like we're trying to overwrite one of them. 14 | -------------------------------------------------------------------------------- /course/2-variables/exercises/8-computed_constants/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const secondsInMinute = 60 7 | const minutesInHour = 60 8 | const secondsInHour = // ? 9 | 10 | // don't edit below this line 11 | fmt.Println("number of seconds in an hour:", secondsInHour) 12 | } 13 | -------------------------------------------------------------------------------- /course/2-variables/exercises/8-computed_constants/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const secondsInMinute = 60 7 | const minutesInHour = 60 8 | const secondsInHour = secondsInMinute * minutesInHour 9 | 10 | // don't edit below this line 11 | fmt.Println("number of seconds in an hour:", secondsInHour) 12 | } 13 | -------------------------------------------------------------------------------- /course/2-variables/exercises/8-computed_constants/expected.txt: -------------------------------------------------------------------------------- 1 | number of seconds in an hour: 3600 2 | -------------------------------------------------------------------------------- /course/2-variables/exercises/9-formatting_strings/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const name = "Saul Goodman" 7 | const openRate = 30.5 8 | 9 | // ? 10 | 11 | // don't edit below this line 12 | 13 | fmt.Println(msg) 14 | } 15 | -------------------------------------------------------------------------------- /course/2-variables/exercises/9-formatting_strings/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const name = "Saul Goodman" 7 | const openRate = 30.5 8 | 9 | msg := fmt.Sprintf("Hi %s, your open rate is %.1f percent", name, openRate) 10 | 11 | // don't edit below this line 12 | 13 | fmt.Println(msg) 14 | } 15 | -------------------------------------------------------------------------------- /course/2-variables/exercises/9-formatting_strings/expected.txt: -------------------------------------------------------------------------------- 1 | Hi Saul Goodman, your open rate is 30.5 percent 2 | -------------------------------------------------------------------------------- /course/3-functions/exercises/1-intro/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func concat(s1, s2) string { 6 | return s1 + s2 7 | } 8 | 9 | // don't touch below this line 10 | 11 | func main() { 12 | test("Lane,", " happy birthday!") 13 | test("Elon,", " hope that Tesla thing works out") 14 | test("Go", " is fantastic") 15 | } 16 | 17 | func test(s1 string, s2 string) { 18 | fmt.Println(concat(s1, s2)) 19 | } 20 | -------------------------------------------------------------------------------- /course/3-functions/exercises/1-intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func concat(s1 string, s2 string) string { 6 | return s1 + s2 7 | } 8 | 9 | // don't touch below this line 10 | 11 | func main() { 12 | test("Lane,", " happy birthday!") 13 | test("Elon,", " hope that Tesla thing works out") 14 | test("Go", " is fantastic") 15 | } 16 | 17 | func test(s1 string, s2 string) { 18 | fmt.Println(concat(s1, s2)) 19 | } 20 | -------------------------------------------------------------------------------- /course/3-functions/exercises/1-intro/expected.txt: -------------------------------------------------------------------------------- 1 | Lane, happy birthday! 2 | Elon, hope that Tesla thing works out 3 | Go is fantastic 4 | -------------------------------------------------------------------------------- /course/3-functions/exercises/2-multiple_params/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which of the following is the most succinct way to write a function signature?", 3 | "answers": [ 4 | "func createUser(firstName, lastName string, age int)", 5 | "func createUser(firstName string, lastName string, age int)" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/3-functions/exercises/2-multiple_params/readme.md: -------------------------------------------------------------------------------- 1 | # Multiple Parameters 2 | 3 | When multiple arguments are of the same type, the type only needs to be declared after the last one, assuming they are in order. 4 | 5 | For example: 6 | 7 | ```go 8 | func add(x, y int) int { 9 | return x + y 10 | } 11 | ``` 12 | 13 | If they are not in order they need to be defined separately. 14 | -------------------------------------------------------------------------------- /course/3-functions/exercises/3-declaration_syntax/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What are we talking about when we discuss 'declaration syntax'?", 3 | "answers": [ 4 | "The style of language used to create new variables, types, functions, etc...", 5 | "The decision about whether to use camelCase or snake_case", 6 | "The argument over guard clauses vs if-else statements", 7 | "The ever-important question of tabs vs spaces" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/3-functions/exercises/3a-declaration_syntax/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which language's declaration syntax reads like English from left-to-right", 3 | "answers": [ 4 | "Go", 5 | "C" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/3-functions/exercises/3b-declaration_syntax/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is 'f func(func(int,int) int, int) int'?", 3 | "answers": [ 4 | "A function named 'f' that takes a function and an int as arguments and returns an int", 5 | "A function named 'f' that takes an int as the argument and returns an int", 6 | "A function named 'f' that takes a function as the argument and returns an int", 7 | "A function named 'f' that takes a function and an int as arguments and returns a function" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/3-functions/exercises/4-pass_by_value/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | sendsSoFar := 430 7 | const sendsToAdd = 25 8 | incrementSends(sendsSoFar, sendsToAdd) 9 | fmt.Println("you've sent", sendsSoFar, "messages") 10 | } 11 | 12 | func incrementSends(sendsSoFar, sendsToAdd int) int { 13 | sendsSoFar = sendsSoFar + sendsToAdd 14 | } 15 | -------------------------------------------------------------------------------- /course/3-functions/exercises/4-pass_by_value/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | sendsSoFar := 430 7 | const sendsToAdd = 25 8 | sendsSoFar = incrementSends(sendsSoFar, sendsToAdd) 9 | fmt.Println("you've sent", sendsSoFar, "messages") 10 | } 11 | 12 | func incrementSends(sendsSoFar, sendsToAdd int) int { 13 | return sendsSoFar + sendsToAdd 14 | } 15 | -------------------------------------------------------------------------------- /course/3-functions/exercises/4-pass_by_value/expected.txt: -------------------------------------------------------------------------------- 1 | you've sent 455 messages 2 | -------------------------------------------------------------------------------- /course/3-functions/exercises/5-ignoring_return_values/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | firstName, lastName := getNames() 7 | fmt.Println("Welcome to Textio,", firstName) 8 | } 9 | 10 | // don't edit below this line 11 | 12 | func getNames() (string, string) { 13 | return "John", "Doe" 14 | } 15 | -------------------------------------------------------------------------------- /course/3-functions/exercises/5-ignoring_return_values/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | firstName, _ := getNames() 7 | fmt.Println("Welcome to Textio,", firstName) 8 | } 9 | 10 | // don't edit below this line 11 | 12 | func getNames() (string, string) { 13 | return "John", "Doe" 14 | } 15 | -------------------------------------------------------------------------------- /course/3-functions/exercises/5-ignoring_return_values/expected.txt: -------------------------------------------------------------------------------- 1 | Welcome to Textio, John 2 | -------------------------------------------------------------------------------- /course/3-functions/exercises/6-named_return_values/expected.txt: -------------------------------------------------------------------------------- 1 | Age: 4 2 | You are an adult in 14 years 3 | You can drink in 17 years 4 | You can rent a car in 21 years 5 | ==================================== 6 | Age: 10 7 | You are an adult in 8 years 8 | You can drink in 11 years 9 | You can rent a car in 15 years 10 | ==================================== 11 | Age: 22 12 | You are an adult in 0 years 13 | You can drink in 0 years 14 | You can rent a car in 3 years 15 | ==================================== 16 | Age: 35 17 | You are an adult in 0 years 18 | You can drink in 0 years 19 | You can rent a car in 0 years 20 | ==================================== 21 | -------------------------------------------------------------------------------- /course/3-functions/exercises/6a-named_returns_explicit/expected.txt: -------------------------------------------------------------------------------- 1 | Age: 4 2 | You are an adult in 14 years 3 | You can drink in 17 years 4 | You can rent a car in 21 years 5 | ==================================== 6 | Age: 10 7 | You are an adult in 8 years 8 | You can drink in 11 years 9 | You can rent a car in 15 years 10 | ==================================== 11 | Age: 22 12 | You are an adult in 0 years 13 | You can drink in 0 years 14 | You can rent a car in 3 years 15 | ==================================== 16 | Age: 35 17 | You are an adult in 0 years 18 | You can drink in 0 years 19 | You can rent a car in 0 years 20 | ==================================== 21 | -------------------------------------------------------------------------------- /course/3-functions/exercises/6a-named_returns_explicit/readme.md: -------------------------------------------------------------------------------- 1 | # Named Return Values - Implicit Returns 2 | 3 | Even though a function has named return values, we can still explicitly return values if we want to. 4 | 5 | ```go 6 | func getCoords() (x, y int){ 7 | return x, y // this is explicit 8 | } 9 | ``` 10 | 11 | Using this explicit pattern we can even overwrite the return values: 12 | 13 | ```go 14 | func getCoords() (x, y int){ 15 | return 5, 6 // this is explicit, x and y are NOT returned 16 | } 17 | ``` 18 | 19 | Otherwise, if we want to return the values defined in the function signature we can just use a naked `return` (blank return): 20 | 21 | ```go 22 | func getCoords() (x, y int){ 23 | return // implicitly returns x and y 24 | } 25 | ``` 26 | 27 | ## Assignment 28 | 29 | Fix the function to return the named values *implicitly*. 30 | -------------------------------------------------------------------------------- /course/3-functions/exercises/6b-when_to_name_returns/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "When should naked returns be used?", 3 | "answers": [ 4 | "For small functions", 5 | "For large functions", 6 | "For complex functions" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/3-functions/exercises/6c-when_to_name_returns/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "When should named returns be used?", 3 | "answers": [ 4 | "When there are many values being returned", 5 | "When there are few parameters being returned", 6 | "When the function is simple" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/3-functions/exercises/7-early_returns/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which is true?", 3 | "answers": [ 4 | "Guard clauses provide a linear approach to logic trees", 5 | "Guard clauses are unreadable", 6 | "Guard clauses are generally worse than nested if/else statements" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/3-functions/exercises/7a-early_returns/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is a guard clause?", 3 | "answers": [ 4 | "An early return from a function when a given condition is met", 5 | "An AND operation in boolean logic", 6 | "A bitwise OR operation" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/4-structs/exercises/1-intro/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type messageToSend struct { 6 | } 7 | 8 | // don't edit below this line 9 | 10 | func test(m messageToSend) { 11 | fmt.Printf("Sending message: '%s' to: %v\n", m.message, m.phoneNumber) 12 | fmt.Println("====================================") 13 | } 14 | 15 | func main() { 16 | test(messageToSend{ 17 | phoneNumber: 148255510981, 18 | message: "Thanks for signing up", 19 | }) 20 | test(messageToSend{ 21 | phoneNumber: 148255510982, 22 | message: "Love to have you aboard!", 23 | }) 24 | test(messageToSend{ 25 | phoneNumber: 148255510983, 26 | message: "We're so excited to have you", 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /course/4-structs/exercises/1-intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type messageToSend struct { 6 | phoneNumber int 7 | message string 8 | } 9 | 10 | // don't edit below this line 11 | 12 | func test(m messageToSend) { 13 | fmt.Printf("Sending message: '%s' to: %v\n", m.message, m.phoneNumber) 14 | fmt.Println("====================================") 15 | } 16 | 17 | func main() { 18 | test(messageToSend{ 19 | phoneNumber: 148255510981, 20 | message: "Thanks for signing up", 21 | }) 22 | test(messageToSend{ 23 | phoneNumber: 148255510982, 24 | message: "Love to have you aboard!", 25 | }) 26 | test(messageToSend{ 27 | phoneNumber: 148255510983, 28 | message: "We're so excited to have you", 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /course/4-structs/exercises/1-intro/expected.txt: -------------------------------------------------------------------------------- 1 | Sending message: 'Thanks for signing up' to: 148255510981 2 | ==================================== 3 | Sending message: 'Love to have you aboard!' to: 148255510982 4 | ==================================== 5 | Sending message: 'We're so excited to have you' to: 148255510983 6 | ==================================== 7 | -------------------------------------------------------------------------------- /course/4-structs/exercises/1-intro/readme.md: -------------------------------------------------------------------------------- 1 | # Structs in Go 2 | 3 | We use structs in Go to represent structured data. It's often convenient to group different types of variables together. For example, if we want to represent a car we could do the following: 4 | 5 | ```go 6 | type car struct { 7 | Make string 8 | Model string 9 | Height int 10 | Width int 11 | } 12 | ``` 13 | 14 | This creates a new struct type called `car`. All cars have a `Make`, `Model`, `Height` and `Width`. 15 | 16 | In Go, you will often use a struct to represent information that you would have used a dictionary for in Python, or an object literal for in JavaScript. 17 | 18 | ## Assignment 19 | 20 | Complete the `messageToSend` struct definition. It needs two fields: 21 | 22 | * `phoneNumber` - an integer 23 | * `message` - a string. 24 | -------------------------------------------------------------------------------- /course/4-structs/exercises/2-nested_structs/expected.txt: -------------------------------------------------------------------------------- 1 | sending "you have an appointment tommorow" from Brenda Halafax (16545550987) to Sally Sue (19035558973)... 2 | ...sent! 3 | ==================================== 4 | sending "you have an event tommorow" from (16545550987) to Suzie Sall (0)... 5 | ...can't send message 6 | ==================================== 7 | sending "you have an party tommorow" from Njorn Halafax (16545550987) to Sally Sue (19035558973)... 8 | ...sent! 9 | ==================================== 10 | sending "you have a birthday tommorow" from Eli Halafax (0) to Whitaker Sue (19035558973)... 11 | ...can't send message 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/4-structs/exercises/3-anonymous_structs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is a good reason to use an anonymous struct?", 3 | "answers": [ 4 | "It is only being used once", 5 | "You're worried about user privacy", 6 | "You're worried about security", 7 | "You need your code to be faster" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/4-structs/exercises/3a-anonymous_structs/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What's one advantage of using an anonymous struct?", 3 | "answers": [ 4 | "Anonymous structs prevent you from re-using a struct definition you never intended to re-use", 5 | "Anonymous structs make your code run faster", 6 | "Anonymous structs can be compiled more quickly" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/4-structs/exercises/4-embedded_structs/expected.txt: -------------------------------------------------------------------------------- 1 | Sender name: Deborah 2 | Sender number: 18055558790 3 | Sender rateLimit: 10000 4 | ==================================== 5 | Sender name: Sarah 6 | Sender number: 19055558790 7 | Sender rateLimit: 5000 8 | ==================================== 9 | Sender name: Sally 10 | Sender number: 19055558790 11 | Sender rateLimit: 1000 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/4-structs/exercises/5-methods/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type authenticationInfo struct { 6 | username string 7 | password string 8 | } 9 | 10 | // ? 11 | 12 | // don't touch below this line 13 | 14 | func test(authInfo authenticationInfo) { 15 | fmt.Println(authInfo.getBasicAuth()) 16 | fmt.Println("====================================") 17 | } 18 | 19 | func main() { 20 | test(authenticationInfo{ 21 | username: "Google", 22 | password: "12345", 23 | }) 24 | test(authenticationInfo{ 25 | username: "Bing", 26 | password: "98765", 27 | }) 28 | test(authenticationInfo{ 29 | username: "DDG", 30 | password: "76921", 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /course/4-structs/exercises/5-methods/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type authenticationInfo struct { 6 | username string 7 | password string 8 | } 9 | 10 | func (authInfo authenticationInfo) getBasicAuth() string { 11 | return "Authorization: Basic " + authInfo.username + ":" + authInfo.password 12 | } 13 | 14 | // don't touch below this line 15 | 16 | func test(authInfo authenticationInfo) { 17 | fmt.Println(authInfo.getBasicAuth()) 18 | fmt.Println("====================================") 19 | } 20 | 21 | func main() { 22 | test(authenticationInfo{ 23 | username: "Google", 24 | password: "12345", 25 | }) 26 | test(authenticationInfo{ 27 | username: "Bing", 28 | password: "98765", 29 | }) 30 | test(authenticationInfo{ 31 | username: "DDG", 32 | password: "76921", 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /course/4-structs/exercises/5-methods/expected.txt: -------------------------------------------------------------------------------- 1 | Authorization: Basic Google:12345 2 | ==================================== 3 | Authorization: Basic Bing:98765 4 | ==================================== 5 | Authorization: Basic DDG:76921 6 | ==================================== 7 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/1-interfaces/expected.txt: -------------------------------------------------------------------------------- 1 | Your "First Report" report is ready. You've sent 10 messages. 2 | ==================================== 3 | Hi John Doe, it is your birthday on 1994-03-21T00:00:00Z 4 | ==================================== 5 | Your "First Report" report is ready. You've sent 10 messages. 6 | ==================================== 7 | Hi Bill Deer, it is your birthday on 1934-05-01T00:00:00Z 8 | ==================================== 9 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/2-implements/expected.txt: -------------------------------------------------------------------------------- 1 | Jack 50000 2 | ==================================== 3 | Bob 7300 4 | ==================================== 5 | Jill 856304 6 | ==================================== 7 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/2-implements/readme.md: -------------------------------------------------------------------------------- 1 | # Interface Implementation 2 | 3 | Interfaces are implemented *implicitly*. 4 | 5 | A type never declares that it implements a given interface. If an interface exists and a type has the proper methods defined, then the type automatically fulfills that interface. 6 | 7 | ## Assignment 8 | 9 | At Textio we have full-time employees and contract employees. We have been tasked with making a more general `employee` interface so that dealing with different employee types is simpler. 10 | 11 | Add the missing `getSalary` method to the `contractor` type so that it fulfills the `employee` interface. 12 | 13 | A contractor's salary is their hourly pay multiplied by how many hours they work per year. 14 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/3-implicit/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How is an interface fulfilled?", 3 | "answers": [ 4 | "A type has all the required interface's methods defined on it", 5 | "A struct embeds the interface in its definition" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/3-implicit/readme.md: -------------------------------------------------------------------------------- 1 | # Interfaces are implemented implicitly 2 | 3 | A type implements an interface by implementing its methods. Unlike in many other languages, there is no explicit declaration of intent, there is no "implements" keyword. 4 | 5 | Implicit interfaces *decouple* the definition of an interface from its implementation. You may add methods to a type and in the process be unknowingly implementing various interfaces, and *that's okay*. 6 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/3a-implicit/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Can a type fulfill multiple interfaces?", 3 | "answers": [ 4 | "Yes, why not?", 5 | "Never" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/3a-implicit/readme.md: -------------------------------------------------------------------------------- 1 | # Interfaces are implemented implicitly 2 | 3 | A type implements an interface by implementing its methods. Unlike in many other languages, there is no explicit declaration of intent, there is no "implements" keyword. 4 | 5 | Implicit interfaces *decouple* the definition of an interface from its implementation. You may add methods to a type and in the process be unknowingly implementing various interfaces, and *that's okay*. 6 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/4-quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Go uses the ____ keyword to show that a type implements an interface", 3 | "answers": [ 4 | "there is no keyword in Go", 5 | "implements", 6 | "fulfills", 7 | "inherits" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/4a-quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "In the example given, the ____ type implements the ____ interface", 3 | "answers": [ 4 | "circle, shape", 5 | "shape, circle", 6 | "circle, area", 7 | "shape, area" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/5-multiple_interfaces/expected.txt: -------------------------------------------------------------------------------- 1 | Printing with cost: $0.11 ... 2 | hello there 3 | ==================================== 4 | Printing with cost: $1.00 ... 5 | I want my money back 6 | ==================================== 7 | Printing with cost: $0.24 ... 8 | Are you free for a chat? 9 | ==================================== 10 | Printing with cost: $1.85 ... 11 | This meeting could have been an email 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/5-multiple_interfaces/readme.md: -------------------------------------------------------------------------------- 1 | # Multiple Interfaces 2 | 3 | A type can implement any number of interfaces in Go. For example, the empty interface, `interface{}`, is *always* implemented by every type because it has no requirements. 4 | 5 | ## Assignment 6 | 7 | Add the required methods so that the `email` type implements both the `expense` and `printer` interfaces. 8 | 9 | ### cost() 10 | 11 | If the email is *not* "subscribed", then the cost is `0.05` for each character in the body. If it *is*, then the cost is `0.01` per character. 12 | 13 | ### print() 14 | 15 | The `print` method should print to standard out the email's body text. 16 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/6-naming_args/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Are you required to name the arguments of an interface in order for your code to compile properly?", 3 | "answers": [ 4 | "No", 5 | "Yes" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/6a-naming_args/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why would you name your interface's method's parameters?", 3 | "answers": [ 4 | "Readability and clarity", 5 | "Execution speed", 6 | "Memory savings" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/7-type_assertion/expected.txt: -------------------------------------------------------------------------------- 1 | Report: The email going to john@does.com will cost: 0.11 2 | ==================================== 3 | Report: The email going to jane@doe.com will cost: 1.85 4 | ==================================== 5 | Report: The email going to elon@doe.com will cost: 1.85 6 | ==================================== 7 | Report: The sms going to +155555509832 will cost: 3.70 8 | ==================================== 9 | Report: The sms going to +155555504444 will cost: 3.70 10 | ==================================== 11 | Report: Invalid expense 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/8-type_switch/expected.txt: -------------------------------------------------------------------------------- 1 | Report: The email going to john@does.com will cost: 0.11 2 | ==================================== 3 | Report: The email going to jane@doe.com will cost: 1.85 4 | ==================================== 5 | Report: The email going to elon@doe.com will cost: 1.05 6 | ==================================== 7 | Report: The sms going to +155555509832 will cost: 8.30 8 | ==================================== 9 | Report: The sms going to +155555504444 will cost: 1.70 10 | ==================================== 11 | Report: Invalid expense 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/9-clean_interfaces/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Interfaces should have as _ methods as possible", 3 | "answers": [ 4 | "Few", 5 | "Many", 6 | "Complex" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/9a-clean_interfaces/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "It's okay for types to be aware of the interfaces they satisfy", 3 | "answers": [ 4 | "True", 5 | "False" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/9b-clean_interfaces/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "It's okay for interfaces to be aware of the types that satisfy them", 3 | "answers": [ 4 | "False", 5 | "True" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/5-interfaces/exercises/9c-clean_interfaces/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Interfaces allow you to define a method's behavior once and use it for many different types", 3 | "answers": [ 4 | "False", 5 | "True" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/6-errors/exercises/1-errors/expected.txt: -------------------------------------------------------------------------------- 1 | Message for customer: Thanks for coming in to our flower shop today! 2 | Message for spouse: We hope you enjoyed your gift. 3 | Error: can't send texts over 25 characters 4 | ======== 5 | Message for customer: Thanks for joining us! 6 | Message for spouse: Have a good day. 7 | Total cost: $0.0076 8 | ======== 9 | Message for customer: Thank you. 10 | Message for spouse: Enjoy! 11 | Total cost: $0.0032 12 | ======== 13 | Message for customer: We loved having you in! 14 | Message for spouse: We hope the rest of your evening is absolutely fantastic. 15 | Error: can't send texts over 25 characters 16 | ======== 17 | -------------------------------------------------------------------------------- /course/6-errors/exercises/2-formatting_strings/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func getSMSErrorString(cost float64, recipient string) string { 4 | // ? 5 | } 6 | 7 | // don't edit below this line 8 | 9 | func test(cost float64, recipient string) { 10 | s := getSMSErrorString(cost, recipient) 11 | fmt.Println(s) 12 | fmt.Println("====================================") 13 | } 14 | 15 | func main() { 16 | test(1.4, "+1 (435) 555 0923") 17 | test(2.1, "+2 (702) 555 3452") 18 | test(32.1, "+1 (801) 555 7456") 19 | test(14.4, "+1 (234) 555 6545") 20 | } 21 | -------------------------------------------------------------------------------- /course/6-errors/exercises/2-formatting_strings/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func getSMSErrorString(cost float64, recipient string) string { 8 | return fmt.Sprintf("SMS that costs $%.2f to be sent to '%v' can not be sent", 9 | cost, 10 | recipient, 11 | ) 12 | } 13 | 14 | // don't edit below this line 15 | 16 | func test(cost float64, recipient string) { 17 | s := getSMSErrorString(cost, recipient) 18 | fmt.Println(s) 19 | fmt.Println("====================================") 20 | } 21 | 22 | func main() { 23 | test(1.4, "+1 (435) 555 0923") 24 | test(2.1, "+2 (702) 555 3452") 25 | test(32.1, "+1 (801) 555 7456") 26 | test(14.4, "+1 (234) 555 6545") 27 | } 28 | -------------------------------------------------------------------------------- /course/6-errors/exercises/2-formatting_strings/expected.txt: -------------------------------------------------------------------------------- 1 | SMS that costs $1.40 to be sent to '+1 (435) 555 0923' can not be sent 2 | ==================================== 3 | SMS that costs $2.10 to be sent to '+2 (702) 555 3452' can not be sent 4 | ==================================== 5 | SMS that costs $32.10 to be sent to '+1 (801) 555 7456' can not be sent 6 | ==================================== 7 | SMS that costs $14.40 to be sent to '+1 (234) 555 6545' can not be sent 8 | ==================================== 9 | -------------------------------------------------------------------------------- /course/6-errors/exercises/3-custom_errors/expected.txt: -------------------------------------------------------------------------------- 1 | Dividing 10.00 by 0.00 ... 2 | can not divide 10 by zero 3 | ==================================== 4 | Dividing 10.00 by 2.00 ... 5 | Quotient: 5.00 6 | ==================================== 7 | Dividing 15.00 by 30.00 ... 8 | Quotient: 0.50 9 | ==================================== 10 | Dividing 6.00 by 3.00 ... 11 | Quotient: 2.00 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/6-errors/exercises/4a-errors_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What is the underlying type of an error?", 3 | "answers": [ 4 | "Interface", 5 | "Struct", 6 | "String" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/6-errors/exercises/4a-errors_quiz/readme.md: -------------------------------------------------------------------------------- 1 | # Errors Quiz 2 | 3 | Go programs express errors with `error` values. Error-values are any type that implements the simple built-in [error interface](https://blog.golang.org/error-handling-and-go). 4 | 5 | Keep in mind that the way Go handles errors is fairly unique. Most languages treat errors as something special and different. For example, Python raises exception types and JavaScript throws and catches errors. In Go, an `error` is just another value that we handle like any other value - however, we want! There aren't any special keywords for dealing with them. 6 | -------------------------------------------------------------------------------- /course/6-errors/exercises/4b-errors_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Can a type be an error and also fulfill another interface?", 3 | "answers": [ 4 | "Yes", 5 | "No" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/6-errors/exercises/4b-errors_quiz/readme.md: -------------------------------------------------------------------------------- 1 | # Errors Quiz 2 | 3 | Go programs express errors with `error` values. Error-values are any type that implements the simple built-in [error interface](https://blog.golang.org/error-handling-and-go). 4 | 5 | Keep in mind that the way Go handles errors is fairly unique. Most languages treat errors as something special and different. For example, Python raises exception types and JavaScript throws and catches errors. In Go, an `error` is just another value that we handle like any other value - however, we want! There aren't any special keywords for dealing with them. 6 | -------------------------------------------------------------------------------- /course/6-errors/exercises/5-errors_package/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func divide(x, y float64) (float64, error) { 9 | if y == 0 { 10 | // ? 11 | } 12 | return x / y, nil 13 | } 14 | 15 | // don't edit below this line 16 | 17 | func test(x, y float64) { 18 | defer fmt.Println("====================================") 19 | fmt.Printf("Dividing %.2f by %.2f ...\n", x, y) 20 | quotient, err := divide(x, y) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | fmt.Printf("Quotient: %.2f\n", quotient) 26 | } 27 | 28 | func main() { 29 | test(10, 0) 30 | test(10, 2) 31 | test(15, 30) 32 | test(6, 3) 33 | } 34 | -------------------------------------------------------------------------------- /course/6-errors/exercises/5-errors_package/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func divide(x, y float64) (float64, error) { 9 | if y == 0 { 10 | return 0, errors.New("no dividing by 0") 11 | } 12 | return x / y, nil 13 | } 14 | 15 | // don't edit below this line 16 | 17 | func test(x, y float64) { 18 | defer fmt.Println("====================================") 19 | fmt.Printf("Dividing %.2f by %.2f ...\n", x, y) 20 | quotient, err := divide(x, y) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | fmt.Printf("Quotient: %.2f\n", quotient) 26 | } 27 | 28 | func main() { 29 | test(10, 0) 30 | test(10, 2) 31 | test(15, 30) 32 | test(6, 3) 33 | } 34 | -------------------------------------------------------------------------------- /course/6-errors/exercises/5-errors_package/expected.txt: -------------------------------------------------------------------------------- 1 | Dividing 10.00 by 0.00 ... 2 | no dividing by 0 3 | ==================================== 4 | Dividing 10.00 by 2.00 ... 5 | Quotient: 5.00 6 | ==================================== 7 | Dividing 15.00 by 30.00 ... 8 | Quotient: 0.50 9 | ==================================== 10 | Dividing 6.00 by 3.00 ... 11 | Quotient: 2.00 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/6-errors/exercises/5-errors_package/readme.md: -------------------------------------------------------------------------------- 1 | # The Errors Package 2 | 3 | The Go standard library provides an "errors" package that makes it easy to deal with errors. 4 | 5 | Read the godoc for the [errors.New()](https://pkg.go.dev/errors#New) function, but here's a simple example: 6 | 7 | ```go 8 | var err error = errors.New("something went wrong") 9 | ``` 10 | 11 | ## Assignment 12 | 13 | Twilio's software architects may have overcomplicated the requirements from the last coding assignment... oops. All we needed was a new generic error message that returns the string `no dividing by 0` when a user attempts to get us to perform the taboo. 14 | 15 | Complete the `divide` function. Use the `errors.New()` function to return an error when `y == 0` that reads "no dividing by 0". 16 | -------------------------------------------------------------------------------- /course/7-loops/exercises/1-intro/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func bulkSend(numMessages int) float64 { 8 | // ? 9 | } 10 | 11 | // don't edit below this line 12 | 13 | func test(numMessages int) { 14 | fmt.Printf("Sending %v messages\n", numMessages) 15 | cost := bulkSend(numMessages) 16 | fmt.Printf("Bulk send complete! Cost = %.2f\n", cost) 17 | fmt.Println("===============================================================") 18 | } 19 | 20 | func main() { 21 | test(10) 22 | test(20) 23 | test(30) 24 | test(40) 25 | test(50) 26 | } 27 | -------------------------------------------------------------------------------- /course/7-loops/exercises/1-intro/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func bulkSend(numMessages int) float64 { 8 | totalCost := 0.0 9 | for i := 0; i < numMessages; i++ { 10 | totalCost += 1 + (float64(i) * 0.01) 11 | } 12 | return totalCost 13 | } 14 | 15 | // don't edit below this line 16 | 17 | func test(numMessages int) { 18 | fmt.Printf("Sending %v messages\n", numMessages) 19 | cost := bulkSend(numMessages) 20 | fmt.Printf("Bulk send complete! Cost = %.2f\n", cost) 21 | fmt.Println("===============================================================") 22 | } 23 | 24 | func main() { 25 | test(10) 26 | test(20) 27 | test(30) 28 | test(40) 29 | test(50) 30 | } 31 | -------------------------------------------------------------------------------- /course/7-loops/exercises/1-intro/expected.txt: -------------------------------------------------------------------------------- 1 | Sending 10 messages 2 | Bulk send complete! Cost = 10.45 3 | =============================================================== 4 | Sending 20 messages 5 | Bulk send complete! Cost = 21.90 6 | =============================================================== 7 | Sending 30 messages 8 | Bulk send complete! Cost = 34.35 9 | =============================================================== 10 | Sending 40 messages 11 | Bulk send complete! Cost = 47.80 12 | =============================================================== 13 | Sending 50 messages 14 | Bulk send complete! Cost = 62.25 15 | =============================================================== 16 | -------------------------------------------------------------------------------- /course/7-loops/exercises/2-omit_condition/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func maxMessages(thresh float64) int { 8 | // ? 9 | } 10 | 11 | // don't edit below this line 12 | 13 | func test(thresh float64) { 14 | fmt.Printf("Threshold: %.2f\n", thresh) 15 | max := maxMessages(thresh) 16 | fmt.Printf("Maximum messages that can be sent: = %v\n", max) 17 | fmt.Println("===============================================================") 18 | } 19 | 20 | func main() { 21 | test(10.00) 22 | test(20.00) 23 | test(30.00) 24 | test(40.00) 25 | test(50.00) 26 | } 27 | -------------------------------------------------------------------------------- /course/7-loops/exercises/2-omit_condition/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func maxMessages(thresh float64) int { 8 | totalCost := 0.0 9 | for i := 0; ; i++ { 10 | totalCost += 1 + (float64(i) * 0.01) 11 | if totalCost > thresh { 12 | return i 13 | } 14 | } 15 | } 16 | 17 | // don't edit below this line 18 | 19 | func test(thresh float64) { 20 | fmt.Printf("Threshold: %.2f\n", thresh) 21 | max := maxMessages(thresh) 22 | fmt.Printf("Maximum messages that can be sent: = %v\n", max) 23 | fmt.Println("===============================================================") 24 | } 25 | 26 | func main() { 27 | test(10.00) 28 | test(20.00) 29 | test(30.00) 30 | test(40.00) 31 | test(50.00) 32 | } 33 | -------------------------------------------------------------------------------- /course/7-loops/exercises/2-omit_condition/expected.txt: -------------------------------------------------------------------------------- 1 | Threshold: 10.00 2 | Maximum messages that can be sent: = 9 3 | =============================================================== 4 | Threshold: 20.00 5 | Maximum messages that can be sent: = 18 6 | =============================================================== 7 | Threshold: 30.00 8 | Maximum messages that can be sent: = 26 9 | =============================================================== 10 | Threshold: 40.00 11 | Maximum messages that can be sent: = 34 12 | =============================================================== 13 | Threshold: 50.00 14 | Maximum messages that can be sent: = 41 15 | =============================================================== 16 | -------------------------------------------------------------------------------- /course/7-loops/exercises/2-omit_condition/readme.md: -------------------------------------------------------------------------------- 1 | # Omitting conditions from a for loop in Go 2 | 3 | Loops in Go can omit sections of a for loop. For example, the `CONDITION` (middle part) can be omitted which causes the loop to run forever. 4 | 5 | ```go 6 | for INITIAL; ; AFTER { 7 | // do something forever 8 | } 9 | ``` 10 | 11 | ## Assignment 12 | 13 | Complete the `maxMessages` function. Given a cost threshold, it should calculate the maximum number of messages that can be sent. 14 | 15 | Each message costs `1.0`, plus an additional fee. The fee structure is: 16 | 17 | * 1st message: `1.0 + 0.00` 18 | * 2nd message: `1.0 + 0.01` 19 | * 3rd message: `1.0 + 0.02` 20 | * 4th message: `1.0 + 0.03` 21 | 22 | ## Browser freeze 23 | 24 | If you lock up your browser by creating an infinite loop that isn't breaking, just click the `cancel` button. 25 | -------------------------------------------------------------------------------- /course/7-loops/exercises/3-while/expected.txt: -------------------------------------------------------------------------------- 1 | Multiplier: 1.1 2 | Max cost: 5 3 | Max messages you can send: 17 4 | ==================================== 5 | Multiplier: 1.3 6 | Max cost: 10 7 | Max messages you can send: 9 8 | ==================================== 9 | Multiplier: 1.35 10 | Max cost: 25 11 | Max messages you can send: 11 12 | ==================================== 13 | -------------------------------------------------------------------------------- /course/7-loops/exercises/4-loops_fizzbuzz/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func fizzbuzz() { 4 | // ? 5 | } 6 | 7 | // don't touch below this line 8 | 9 | func main() { 10 | fizzbuzz() 11 | } 12 | -------------------------------------------------------------------------------- /course/7-loops/exercises/4-loops_fizzbuzz/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func fizzbuzz() { 6 | for i := 1; i < 101; i++ { 7 | if i%3 == 0 && i%5 == 0 { 8 | fmt.Println("fizzbuzz") 9 | } else if i%3 == 0 { 10 | fmt.Println("fizz") 11 | } else if i%5 == 0 { 12 | fmt.Println("buzz") 13 | } else { 14 | fmt.Println(i) 15 | } 16 | } 17 | } 18 | 19 | // don't touch below this line 20 | 21 | func main() { 22 | fizzbuzz() 23 | } 24 | -------------------------------------------------------------------------------- /course/7-loops/exercises/5-continue_and_break/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func printPrimes(max int) { 8 | // ? 9 | } 10 | 11 | // don't edit below this line 12 | 13 | func test(max int) { 14 | fmt.Printf("Primes up to %v:\n", max) 15 | printPrimes(max) 16 | fmt.Println("===============================================================") 17 | } 18 | 19 | func main() { 20 | test(10) 21 | test(20) 22 | test(30) 23 | } 24 | -------------------------------------------------------------------------------- /course/7-loops/exercises/5-continue_and_break/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func printPrimes(max int) { 8 | for n := 2; n <= max; n++ { 9 | if n == 2 { 10 | fmt.Println(n) 11 | continue 12 | } 13 | 14 | if n%2 == 0 { 15 | continue 16 | } 17 | 18 | isPrime := true 19 | for i := 3; i*i <= n; i++ { 20 | if n%i == 0 { 21 | isPrime = false 22 | break 23 | } 24 | } 25 | if isPrime { 26 | fmt.Println(n) 27 | } 28 | } 29 | } 30 | 31 | // don't edit below this line 32 | 33 | func test(max int) { 34 | fmt.Printf("Primes up to %v:\n", max) 35 | printPrimes(max) 36 | fmt.Println("===============================================================") 37 | } 38 | 39 | func main() { 40 | test(10) 41 | test(20) 42 | test(30) 43 | } 44 | -------------------------------------------------------------------------------- /course/7-loops/exercises/5-continue_and_break/expected.txt: -------------------------------------------------------------------------------- 1 | Primes up to 10: 2 | 2 3 | 3 4 | 5 5 | 7 6 | =============================================================== 7 | Primes up to 20: 8 | 2 9 | 3 10 | 5 11 | 7 12 | 11 13 | 13 14 | 17 15 | 19 16 | =============================================================== 17 | Primes up to 30: 18 | 2 19 | 3 20 | 5 21 | 7 22 | 11 23 | 13 24 | 17 25 | 19 26 | 23 27 | 29 28 | =============================================================== 29 | -------------------------------------------------------------------------------- /course/8-slices/exercises/1-arrays/expected.txt: -------------------------------------------------------------------------------- 1 | sending to Bob... 2 | sending: "click here to sign up" 3 | they responded! 4 | sending to Alice... 5 | sending: "click here to sign up" 6 | sending: "pretty please click here" 7 | they responded! 8 | sending to Mangalam... 9 | sending: "click here to sign up" 10 | sending: "pretty please click here" 11 | sending: "we beg you to sign up" 12 | they responded! 13 | sending to Ozgur... 14 | sending: "click here to sign up" 15 | sending: "pretty please click here" 16 | sending: "we beg you to sign up" 17 | complete failure 18 | -------------------------------------------------------------------------------- /course/8-slices/exercises/10-slice_gotcha/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why is 5 the final value in the last index of array 'j'?", 3 | "answers": [ 4 | "j and g point to the same underlying array so g's append overwrote j", 5 | "The Go team be trollin'", 6 | "Because append only works properly when the number of elements is < 10" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/8-slices/exercises/10a-slice_gotcha/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Why doesn't the bug regarding slices 'j' and 'g' in example 2 occur in example 1 as well?", 3 | "answers": [ 4 | "The array's cap() is exceeded so a new underlying array is allocated", 5 | "Because there are fewer elements and Go's runtime can't handle more than ~8" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/10b-slice_gotcha/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "How can you best avoid these kinds of bugs?", 3 | "answers": [ 4 | "Always assign the result of the append() function back to the same slice", 5 | "Always assign the result of the append() function to a new slice", 6 | "Don't use the append() function" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/8-slices/exercises/11-range/expected.txt: -------------------------------------------------------------------------------- 1 | Scanning message: [hey there john] for bad words: 2 | - crap 3 | - shoot 4 | - dang 5 | - frick 6 | Index: -1 7 | ==================================== 8 | Scanning message: [ugh oh my frick] for bad words: 9 | - crap 10 | - shoot 11 | - dang 12 | - frick 13 | Index: 3 14 | ==================================== 15 | Scanning message: [what the shoot I hate that crap] for bad words: 16 | - crap 17 | - shoot 18 | - dang 19 | - frick 20 | Index: 2 21 | ==================================== 22 | -------------------------------------------------------------------------------- /course/8-slices/exercises/11-range/readme.md: -------------------------------------------------------------------------------- 1 | # Range 2 | 3 | Go provides syntactic sugar to iterate easily over elements of a slice: 4 | 5 | ```go 6 | for INDEX, ELEMENT := range SLICE { 7 | } 8 | ``` 9 | 10 | For example: 11 | 12 | ```go 13 | fruits := []string{"apple", "banana", "grape"} 14 | for i, fruit := range fruits { 15 | fmt.Println(i, fruit) 16 | } 17 | // 0 apple 18 | // 1 banana 19 | // 2 grape 20 | ``` 21 | 22 | ## Assignment 23 | 24 | We need to be able to quickly detect bad words in the messages our system sends. 25 | 26 | Complete the `indexOfFirstBadWord` function. If it finds any bad words in the message it should return the **index** of the first bad word in the `msg` slice. This will help us filter out naughty words from our messaging system. If no bad words are found, return `-1` instead. 27 | 28 | Use the `range` keyword. 29 | -------------------------------------------------------------------------------- /course/8-slices/exercises/2-slices/expected.txt: -------------------------------------------------------------------------------- 1 | sending to Ozgur... 2 | sending: "click here to sign up" 3 | sending: "pretty please click here" 4 | no response 5 | ===================================== 6 | sending to Jeff... 7 | sending: "click here to sign up" 8 | sending: "pretty please click here" 9 | sending: "we beg you to sign up" 10 | no response 11 | ===================================== 12 | sending to Sally... 13 | sending: "click here to sign up" 14 | sending: "pretty please click here" 15 | sending: "we beg you to sign up" 16 | they responded! 17 | ===================================== 18 | sending to Sally... 19 | Error: unsupported plan 20 | ===================================== 21 | -------------------------------------------------------------------------------- /course/8-slices/exercises/3-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which references the other?", 3 | "answers": [ 4 | "Slices reference arrays", 5 | "Arrays reference slices" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/3a-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Multiple slices can point to the same array", 3 | "answers": [ 4 | "True", 5 | "False" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/3b-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "A function that only has access to a slice can modify the underlying array", 3 | "answers": [ 4 | "True", 5 | "False" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/4-slices_no_array/expected.txt: -------------------------------------------------------------------------------- 1 | Messages: 2 | - Welcome to the movies! 3 | - Enjoy your popcorn! 4 | - Please don't talk during the movie! 5 | Costs: 6 | - 0.22 7 | - 0.19 8 | - 0.35 9 | ===== END REPORT ===== 10 | Messages: 11 | - I don't want to be here anymore 12 | - Can we go home? 13 | - I'm hungry 14 | - I'm bored 15 | Costs: 16 | - 0.31 17 | - 0.15 18 | - 0.10 19 | - 0.09 20 | ===== END REPORT ===== 21 | Messages: 22 | - Hello 23 | - Hi 24 | - Hey 25 | - Hi there 26 | - Hey there 27 | - Hi there 28 | - Hello there 29 | - Hey there 30 | - Hello there 31 | - General Kenobi 32 | Costs: 33 | - 0.05 34 | - 0.02 35 | - 0.03 36 | - 0.08 37 | - 0.09 38 | - 0.08 39 | - 0.11 40 | - 0.09 41 | - 0.11 42 | - 0.14 43 | ===== END REPORT ===== 44 | -------------------------------------------------------------------------------- /course/8-slices/exercises/5-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the cap() function return?", 3 | "answers": [ 4 | "The maximum length of the slice before reallocation of the array is necessary", 5 | "The last element of the slice" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/5a-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the len() function return?", 3 | "answers": [ 4 | "The current length of the slice", 5 | "The maximum length of the slice before reallocation of the array is necessary" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/5b-slices_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What do len() and cap() do when a slice is nil?", 3 | "answers": [ 4 | "Return 0", 5 | "Panic" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/8-slices/exercises/7-variadic_functions/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func sum(nums ...float64) float64 { 6 | // ? 7 | } 8 | 9 | // don't edit below this line 10 | 11 | func test(nums ...float64) { 12 | total := sum(nums...) 13 | fmt.Printf("Summing %v costs...\n", len(nums)) 14 | fmt.Printf("Bill for the month: %.2f\n", total) 15 | fmt.Println("===== END REPORT =====") 16 | } 17 | 18 | func main() { 19 | test(1.0, 2.0, 3.0) 20 | test(1.0, 2.0, 3.0, 4.0, 5.0) 21 | test(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) 22 | test(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0) 23 | } 24 | -------------------------------------------------------------------------------- /course/8-slices/exercises/7-variadic_functions/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func sum(nums ...float64) float64 { 6 | sum := 0.0 7 | for i := 0; i < len(nums); i++ { 8 | num := nums[i] 9 | sum += num 10 | } 11 | return sum 12 | } 13 | 14 | // don't edit below this line 15 | 16 | func test(nums ...float64) { 17 | total := sum(nums...) 18 | fmt.Printf("Summing %v costs...\n", len(nums)) 19 | fmt.Printf("Bill for the month: %.2f\n", total) 20 | fmt.Println("===== END REPORT =====") 21 | } 22 | 23 | func main() { 24 | test(1.0, 2.0, 3.0) 25 | test(1.0, 2.0, 3.0, 4.0, 5.0) 26 | test(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) 27 | test(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0) 28 | } 29 | -------------------------------------------------------------------------------- /course/8-slices/exercises/7-variadic_functions/expected.txt: -------------------------------------------------------------------------------- 1 | Summing 3 costs... 2 | Bill for the month: 6.00 3 | ===== END REPORT ===== 4 | Summing 5 costs... 5 | Bill for the month: 15.00 6 | ===== END REPORT ===== 7 | Summing 10 costs... 8 | Bill for the month: 55.00 9 | ===== END REPORT ===== 10 | Summing 15 costs... 11 | Bill for the month: 120.00 12 | ===== END REPORT ===== 13 | -------------------------------------------------------------------------------- /course/8-slices/exercises/9-2d_slices/code.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func createMatrix(rows, cols int) [][]int { 6 | // ? 7 | } 8 | 9 | // dont edit below this line 10 | 11 | func test(rows, cols int) { 12 | fmt.Printf("Creating %v x %v matrix...\n", rows, cols) 13 | matrix := createMatrix(rows, cols) 14 | for i := 0; i < len(matrix); i++ { 15 | fmt.Println(matrix[i]) 16 | } 17 | fmt.Println("===== END REPORT =====") 18 | } 19 | 20 | func main() { 21 | test(3, 3) 22 | test(5, 5) 23 | test(10, 10) 24 | test(15, 15) 25 | } 26 | -------------------------------------------------------------------------------- /course/8-slices/exercises/9-2d_slices/complete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func createMatrix(rows, cols int) [][]int { 6 | matrix := [][]int{} 7 | for i := 0; i < rows; i++ { 8 | row := []int{} 9 | for j := 0; j < cols; j++ { 10 | row = append(row, i*j) 11 | } 12 | matrix = append(matrix, row) 13 | } 14 | return matrix 15 | } 16 | 17 | // dont edit below this line 18 | 19 | func test(rows, cols int) { 20 | fmt.Printf("Creating %v x %v matrix...\n", rows, cols) 21 | matrix := createMatrix(rows, cols) 22 | for i := 0; i < len(matrix); i++ { 23 | fmt.Println(matrix[i]) 24 | } 25 | fmt.Println("===== END REPORT =====") 26 | } 27 | 28 | func main() { 29 | test(3, 3) 30 | test(5, 5) 31 | test(10, 10) 32 | test(15, 15) 33 | } 34 | -------------------------------------------------------------------------------- /course/9-maps/exercises/1-maps/expected.txt: -------------------------------------------------------------------------------- 1 | Creating map... 2 | key: John, value: 3 | - name: John 4 | - number: 14355550987 5 | key: Bob, value: 6 | - name: Bob 7 | - number: 98765550987 8 | key: Jill, value: 9 | - name: Jill 10 | - number: 18265554567 11 | ==================================== 12 | Creating map... 13 | invalid sizes 14 | ==================================== 15 | Creating map... 16 | key: George, value: 17 | - name: George 18 | - number: 20955559812 19 | key: Sally, value: 20 | - name: Sally 21 | - number: 38385550982 22 | key: Rich, value: 23 | - name: Rich 24 | - number: 48265554567 25 | key: Sue, value: 26 | - name: Sue 27 | - number: 16045559873 28 | ==================================== 29 | -------------------------------------------------------------------------------- /course/9-maps/exercises/2-mutating_maps/expected.txt: -------------------------------------------------------------------------------- 1 | Attempting to delete john... 2 | Deleted: john 3 | ==================================== 4 | Attempting to delete musk... 5 | not found 6 | ==================================== 7 | Attempting to delete santa... 8 | not found 9 | ==================================== 10 | Attempting to delete kade... 11 | Did not delete: kade 12 | ==================================== 13 | Final map keys: 14 | - breanna 15 | - elon 16 | - kade 17 | -------------------------------------------------------------------------------- /course/9-maps/exercises/3-map_keys/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What makes a type qualify to be used as a map key?", 3 | "answers": [ 4 | "The type is comparable", 5 | "The type is basic", 6 | "The type is Go native; not custom" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/9-maps/exercises/3a-map_keys/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Which is simpler?", 3 | "answers": [ 4 | "To use a struct directly as a key", 5 | "To nest maps" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/9-maps/exercises/4-maps_count/expected.txt: -------------------------------------------------------------------------------- 1 | Generating counts for 10000 user IDs... 2 | Counts from select IDs: 3 | - 00: 31 4 | - ff: 27 5 | - dd: 37 6 | ===================================== 7 | Generating counts for 10000 user IDs... 8 | Counts from select IDs: 9 | - aa: 29 10 | - 12: 29 11 | - 32: 41 12 | ===================================== 13 | Generating counts for 10000 user IDs... 14 | Counts from select IDs: 15 | - bb: 28 16 | - 33: 46 17 | ===================================== 18 | -------------------------------------------------------------------------------- /course/9-maps/exercises/4-maps_count/readme.md: -------------------------------------------------------------------------------- 1 | # Count Instances 2 | 3 | Remember that you can check if a key is already present in a map by using the second return value from the index operation. 4 | 5 | ```go 6 | names := map[string]int{} 7 | 8 | if _, ok := names["elon"]; !ok { 9 | // if the key doesn't exist yet, 10 | // initialize its value to 0 11 | names["elon"] = 0 12 | } 13 | ``` 14 | 15 | ## Assignment 16 | 17 | We have a slice of user ids, and each instance of an id in the slice indicates that a message was sent to that user. We need to count up how many times each user's id appears in the slice to track how many messages they received. 18 | 19 | Implement the `getCounts` function. It should return a map of `string -> int` so that each `int` is a count of how many times each string was found in the slice. 20 | -------------------------------------------------------------------------------- /course/9-maps/exercises/5a-maps_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Maps can have at most ____ value(s) associated with the same key", 3 | "answers": [ 4 | "1", 5 | "2", 6 | "3", 7 | "any number of" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /course/9-maps/exercises/5b-maps_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Attempting to get a value from a map where the key doesn't exist...", 3 | "answers": [ 4 | "Returns the zero value", 5 | "Panics", 6 | "Returns the closest value" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/9-maps/exercises/5c-maps_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "A function can mutate the values stored in a map and those changes ____ the caller", 3 | "answers": [ 4 | "affect", 5 | "do not affect" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /course/9-maps/exercises/5d-maps_quiz/multiple_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "What does the second return value from a retrieve operation in a map indicate?", 3 | "answers": [ 4 | "A boolean that indicates whether the key exists", 5 | "A boolean that indicates whether the value at the key is a nil value", 6 | "A boolean that indicates whether the value at the key is a zero value" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /course/9-maps/exercises/6-nested_maps/expected.txt: -------------------------------------------------------------------------------- 1 | Generating counts for 50 names... 2 | Count for [M][Matthew]: 3 3 | ===================================== 4 | Generating counts for 100 names... 5 | Count for [G][George]: 1 6 | ===================================== 7 | Generating counts for 150 names... 8 | Count for [D][Drew]: 4 9 | ===================================== 10 | Generating counts for 200 names... 11 | Count for [P][Philip]: 4 12 | ===================================== 13 | Generating counts for 250 names... 14 | Count for [B][Bryant]: 1 15 | ===================================== 16 | Generating counts for 300 names... 17 | Count for [M][Matthew]: 6 18 | ===================================== 19 | -------------------------------------------------------------------------------- /project/10-posts/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/10-posts/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/posts 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/google/uuid v1.3.0 9 | github.com/joho/godotenv v1.5.1 10 | github.com/lib/pq v1.10.7 11 | ) 12 | -------------------------------------------------------------------------------- /project/10-posts/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 6 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 7 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 8 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 9 | github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= 10 | github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 11 | -------------------------------------------------------------------------------- /project/10-posts/src/handler_posts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/bootdotdev/projects/posts/internal/database" 8 | ) 9 | 10 | func (cfg *apiConfig) handlerPostsGet(w http.ResponseWriter, r *http.Request, user database.User) { 11 | limitStr := r.URL.Query().Get("limit") 12 | limit := 10 13 | if specifiedLimit, err := strconv.Atoi(limitStr); err == nil { 14 | limit = specifiedLimit 15 | } 16 | 17 | posts, err := cfg.DB.GetPostsForUser(r.Context(), database.GetPostsForUserParams{ 18 | UserID: user.ID, 19 | Limit: int32(limit), 20 | }) 21 | if err != nil { 22 | respondWithError(w, http.StatusInternalServerError, "Couldn't get posts for user") 23 | return 24 | } 25 | 26 | respondWithJSON(w, http.StatusOK, databasePostsToPosts(posts)) 27 | } 28 | -------------------------------------------------------------------------------- /project/10-posts/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/10-posts/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/10-posts/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/10-posts/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/10-posts/src/middleware_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bootdotdev/projects/posts/internal/auth" 7 | "github.com/bootdotdev/projects/posts/internal/database" 8 | ) 9 | 10 | type authedHandler func(http.ResponseWriter, *http.Request, database.User) 11 | 12 | func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | apiKey, err := auth.GetAPIKey(r.Header) 15 | if err != nil { 16 | respondWithError(w, http.StatusUnauthorized, "Couldn't find api key") 17 | return 18 | } 19 | 20 | user, err := cfg.DB.GetUserByAPIKey(r.Context(), apiKey) 21 | if err != nil { 22 | respondWithError(w, http.StatusNotFound, "Couldn't get user") 23 | return 24 | } 25 | 26 | handler(w, r, user) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/queries/feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- name: GetFeedFollowsForUser :many 2 | SELECT * FROM feed_follows WHERE user_id = $1; 3 | -- 4 | 5 | -- name: CreateFeedFollow :one 6 | INSERT INTO feed_follows (id, created_at, updated_at, user_id, feed_id) 7 | VALUES ($1, $2, $3, $4, $5) 8 | RETURNING *; 9 | -- 10 | 11 | -- name: DeleteFeedFollow :exec 12 | DELETE FROM feed_follows WHERE id = $1 and user_id = $2; 13 | -- 14 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/queries/feeds.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateFeed :one 2 | INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) 3 | VALUES ($1, $2, $3, $4, $5, $6) 4 | RETURNING *; 5 | 6 | -- name: GetFeeds :many 7 | SELECT * FROM feeds; 8 | 9 | -- name: GetNextFeedsToFetch :many 10 | SELECT * FROM feeds 11 | ORDER BY last_fetched_at ASC NULLS FIRST 12 | LIMIT $1; 13 | 14 | -- name: MarkFeedFetched :one 15 | UPDATE feeds 16 | SET last_fetched_at = NOW(), 17 | updated_at = NOW() 18 | WHERE id = $1 19 | RETURNING *; 20 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/queries/posts.sql: -------------------------------------------------------------------------------- 1 | -- name: CreatePost :one 2 | INSERT INTO posts (id, created_at, updated_at, title, url, description, published_at, feed_id) 3 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 4 | RETURNING *; 5 | -- 6 | 7 | -- name: GetPostsForUser :many 8 | SELECT posts.* FROM posts 9 | JOIN feed_follows ON feed_follows.feed_id = posts.feed_id 10 | WHERE feed_follows.user_id = $1 11 | ORDER BY posts.published_at DESC 12 | LIMIT $2; 13 | -- 14 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/003_feeds.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feeds ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feeds; 13 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/004_feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feed_follows ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, 7 | feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE, 8 | UNIQUE (user_id, feed_id) 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feed_follows; 13 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/005_feed_lastfetched.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE feeds ADD COLUMN last_fetched_at TIMESTAMP; 3 | 4 | -- +goose Down 5 | ALTER TABLE feeds DROP COLUMN last_fetched_at; 6 | -------------------------------------------------------------------------------- /project/10-posts/src/sql/schema/006_posts.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE posts ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | title TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | description TEXT, 9 | published_at TIMESTAMP, 10 | feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE 11 | ); 12 | 13 | -- +goose Down 14 | DROP TABLE posts; 15 | -------------------------------------------------------------------------------- /project/10-posts/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/google/uuid/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4.3 5 | - 1.5.3 6 | - tip 7 | 8 | script: 9 | - go test -v ./... 10 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/google/uuid/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We definitely welcome patches and contribution to this project! 4 | 5 | ### Legal requirements 6 | 7 | In order to protect both you and ourselves, you will need to sign the 8 | [Contributor License Agreement](https://cla.developers.google.com/clas). 9 | 10 | You may have already signed it for other Google projects. 11 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/google/uuid/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Paul Borman 2 | bmatsuo 3 | shawnps 4 | theory 5 | jboverfelt 6 | dsymonds 7 | cd1 8 | wallclockbuilder 9 | dansouza 10 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/google/uuid/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package uuid generates and inspects UUIDs. 6 | // 7 | // UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security 8 | // Services. 9 | // 10 | // A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to 11 | // maps or compared directly. 12 | package uuid 13 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/google/uuid/node_js.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build js 6 | 7 | package uuid 8 | 9 | // getHardwareInterface returns nil values for the JS version of the code. 10 | // This remvoves the "net" dependency, because it is not used in the browser. 11 | // Using the "net" library inflates the size of the transpiled JS code by 673k bytes. 12 | func getHardwareInterface(name string) (string, []byte) { return "", nil } 13 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/.gitignore: -------------------------------------------------------------------------------- 1 | .db 2 | *.test 3 | *~ 4 | *.swp 5 | .idea 6 | .vscode -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/TESTS.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## Running Tests 4 | 5 | `go test` is used for testing. A running PostgreSQL 6 | server is required, with the ability to log in. The 7 | database to connect to test with is "pqgotest," on 8 | "localhost" but these can be overridden using [environment 9 | variables](https://www.postgresql.org/docs/9.3/static/libpq-envars.html). 10 | 11 | Example: 12 | 13 | PGHOST=/run/postgresql go test 14 | 15 | ## Benchmarks 16 | 17 | A benchmark suite can be run as part of the tests: 18 | 19 | go test -bench . 20 | 21 | ## Example setup (Docker) 22 | 23 | Run a postgres container: 24 | 25 | ``` 26 | docker run --expose 5432:5432 postgres 27 | ``` 28 | 29 | Run tests: 30 | 31 | ``` 32 | PGHOST=localhost PGPORT=5432 PGUSER=postgres PGSSLMODE=disable PGDATABASE=postgres go test 33 | ``` 34 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/oid/doc.go: -------------------------------------------------------------------------------- 1 | // Package oid contains OID constants 2 | // as defined by the Postgres server. 3 | package oid 4 | 5 | // Oid is a Postgres Object ID. 6 | type Oid uint32 7 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/ssl_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package pq 5 | 6 | // sslKeyPermissions checks the permissions on user-supplied ssl key files. 7 | // The key file should have very little access. 8 | // 9 | // libpq does not check key file permissions on Windows. 10 | func sslKeyPermissions(string) error { return nil } 11 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/user_other.go: -------------------------------------------------------------------------------- 1 | // Package pq is a pure Go Postgres driver for the database/sql package. 2 | 3 | //go:build js || android || hurd || zos 4 | // +build js android hurd zos 5 | 6 | package pq 7 | 8 | func userCurrent() (string, error) { 9 | return "", ErrCouldNotDetectUsername 10 | } 11 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/user_posix.go: -------------------------------------------------------------------------------- 1 | // Package pq is a pure Go Postgres driver for the database/sql package. 2 | 3 | //go:build aix || darwin || dragonfly || freebsd || (linux && !android) || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos 4 | // +build aix darwin dragonfly freebsd linux,!android nacl netbsd openbsd plan9 solaris rumprun illumos 5 | 6 | package pq 7 | 8 | import ( 9 | "os" 10 | "os/user" 11 | ) 12 | 13 | func userCurrent() (string, error) { 14 | u, err := user.Current() 15 | if err == nil { 16 | return u.Username, nil 17 | } 18 | 19 | name := os.Getenv("USER") 20 | if name != "" { 21 | return name, nil 22 | } 23 | 24 | return "", ErrCouldNotDetectUsername 25 | } 26 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/github.com/lib/pq/uuid.go: -------------------------------------------------------------------------------- 1 | package pq 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | // decodeUUIDBinary interprets the binary format of a uuid, returning it in text format. 9 | func decodeUUIDBinary(src []byte) ([]byte, error) { 10 | if len(src) != 16 { 11 | return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src)) 12 | } 13 | 14 | dst := make([]byte, 36) 15 | dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-' 16 | hex.Encode(dst[0:], src[0:4]) 17 | hex.Encode(dst[9:], src[4:6]) 18 | hex.Encode(dst[14:], src[6:8]) 19 | hex.Encode(dst[19:], src[8:10]) 20 | hex.Encode(dst[24:], src[10:16]) 21 | 22 | return dst, nil 23 | } 24 | -------------------------------------------------------------------------------- /project/10-posts/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/google/uuid v1.3.0 8 | ## explicit 9 | github.com/google/uuid 10 | # github.com/joho/godotenv v1.5.1 11 | ## explicit; go 1.12 12 | github.com/joho/godotenv 13 | # github.com/lib/pq v1.10.7 14 | ## explicit; go 1.13 15 | github.com/lib/pq 16 | github.com/lib/pq/oid 17 | github.com/lib/pq/scram 18 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/boilerplate 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/2-boilerplate/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/4-create_users/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/4-create_users/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/createusers 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/4-create_users/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/4-create_users/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/4-create_users/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/4-create_users/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type User struct { 14 | ID uuid.UUID 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | Name string 18 | } 19 | -------------------------------------------------------------------------------- /project/4-create_users/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/4-create_users/src/models.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bootdotdev/projects/createusers/internal/database" 7 | "github.com/google/uuid" 8 | ) 9 | 10 | type User struct { 11 | ID uuid.UUID `json:"id"` 12 | CreatedAt time.Time `json:"created_at"` 13 | UpdatedAt time.Time `json:"updated_at"` 14 | Name string `json:"name"` 15 | } 16 | 17 | func databaseUserToUser(user database.User) User { 18 | return User{ 19 | ID: user.ID, 20 | CreatedAt: user.CreatedAt, 21 | UpdatedAt: user.UpdatedAt, 22 | Name: user.Name, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /project/4-create_users/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name) 3 | VALUES ($1, $2, $3, $4) 4 | RETURNING *; 5 | -------------------------------------------------------------------------------- /project/4-create_users/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/4-create_users/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/4-create_users/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/4-create_users/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/4-create_users/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/4-create_users/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/5-apikey/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/5-apikey/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/apikey 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/5-apikey/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/5-apikey/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/5-apikey/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/5-apikey/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/5-apikey/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type User struct { 14 | ID uuid.UUID 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | Name string 18 | ApiKey string 19 | } 20 | -------------------------------------------------------------------------------- /project/5-apikey/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/5-apikey/src/models.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bootdotdev/projects/apikey/internal/database" 7 | "github.com/google/uuid" 8 | ) 9 | 10 | type User struct { 11 | ID uuid.UUID `json:"id"` 12 | CreatedAt time.Time `json:"created_at"` 13 | UpdatedAt time.Time `json:"updated_at"` 14 | Name string `json:"name"` 15 | ApiKey string `json:"api_key"` 16 | } 17 | 18 | func databaseUserToUser(user database.User) User { 19 | return User{ 20 | ID: user.ID, 21 | CreatedAt: user.CreatedAt, 22 | UpdatedAt: user.UpdatedAt, 23 | Name: user.Name, 24 | ApiKey: user.ApiKey, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /project/5-apikey/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/5-apikey/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/5-apikey/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/5-apikey/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/5-apikey/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/5-apikey/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/5-apikey/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/5-apikey/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/6-createfeed/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/6-createfeed/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/createfeed 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/6-createfeed/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/6-createfeed/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/6-createfeed/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/6-createfeed/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/6-createfeed/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type Feed struct { 14 | ID uuid.UUID 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | Name string 18 | Url string 19 | UserID uuid.UUID 20 | } 21 | 22 | type User struct { 23 | ID uuid.UUID 24 | CreatedAt time.Time 25 | UpdatedAt time.Time 26 | Name string 27 | ApiKey string 28 | } 29 | -------------------------------------------------------------------------------- /project/6-createfeed/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/6-createfeed/src/middleware_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bootdotdev/projects/createfeed/internal/auth" 7 | "github.com/bootdotdev/projects/createfeed/internal/database" 8 | ) 9 | 10 | type authedHandler func(http.ResponseWriter, *http.Request, database.User) 11 | 12 | func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | apiKey, err := auth.GetAPIKey(r.Header) 15 | if err != nil { 16 | respondWithError(w, http.StatusUnauthorized, "Couldn't find api key") 17 | return 18 | } 19 | 20 | user, err := cfg.DB.GetUserByAPIKey(r.Context(), apiKey) 21 | if err != nil { 22 | respondWithError(w, http.StatusNotFound, "Couldn't get user") 23 | return 24 | } 25 | 26 | handler(w, r, user) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sql/queries/feeds.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateFeed :one 2 | INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) 3 | VALUES ($1, $2, $3, $4, $5, $6) 4 | RETURNING *; 5 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sql/schema/003_feeds.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feeds ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feeds; 13 | -------------------------------------------------------------------------------- /project/6-createfeed/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/6-createfeed/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/6-createfeed/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/6-createfeed/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/6-createfeed/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/7-getfeeds/readme.md: -------------------------------------------------------------------------------- 1 | # Get all feeds 2 | 3 | Create a new endpoint to retrieve *all* of the feeds in the database. This endpoint should *not* require authentication. 4 | 5 | You should be familiar with all of the steps to make this happen by now, use your other endpoints as a reference. 6 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/getfeeds 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type Feed struct { 14 | ID uuid.UUID 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | Name string 18 | Url string 19 | UserID uuid.UUID 20 | } 21 | 22 | type User struct { 23 | ID uuid.UUID 24 | CreatedAt time.Time 25 | UpdatedAt time.Time 26 | Name string 27 | ApiKey string 28 | } 29 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/middleware_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bootdotdev/projects/getfeeds/internal/auth" 7 | "github.com/bootdotdev/projects/getfeeds/internal/database" 8 | ) 9 | 10 | type authedHandler func(http.ResponseWriter, *http.Request, database.User) 11 | 12 | func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | apiKey, err := auth.GetAPIKey(r.Header) 15 | if err != nil { 16 | respondWithError(w, http.StatusUnauthorized, "Couldn't find api key") 17 | return 18 | } 19 | 20 | user, err := cfg.DB.GetUserByAPIKey(r.Context(), apiKey) 21 | if err != nil { 22 | respondWithError(w, http.StatusNotFound, "Couldn't get user") 23 | return 24 | } 25 | 26 | handler(w, r, user) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sql/queries/feeds.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateFeed :one 2 | INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) 3 | VALUES ($1, $2, $3, $4, $5, $6) 4 | RETURNING *; 5 | 6 | -- name: GetFeeds :many 7 | SELECT * FROM feeds; 8 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sql/schema/003_feeds.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feeds ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feeds; 13 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/7-getfeeds/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/feedfollows 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type Feed struct { 14 | ID uuid.UUID 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | Name string 18 | Url string 19 | UserID uuid.UUID 20 | } 21 | 22 | type FeedFollow struct { 23 | ID uuid.UUID 24 | CreatedAt time.Time 25 | UpdatedAt time.Time 26 | UserID uuid.UUID 27 | FeedID uuid.UUID 28 | } 29 | 30 | type User struct { 31 | ID uuid.UUID 32 | CreatedAt time.Time 33 | UpdatedAt time.Time 34 | Name string 35 | ApiKey string 36 | } 37 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/middleware_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bootdotdev/projects/feedfollows/internal/auth" 7 | "github.com/bootdotdev/projects/feedfollows/internal/database" 8 | ) 9 | 10 | type authedHandler func(http.ResponseWriter, *http.Request, database.User) 11 | 12 | func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | apiKey, err := auth.GetAPIKey(r.Header) 15 | if err != nil { 16 | respondWithError(w, http.StatusUnauthorized, "Couldn't find api key") 17 | return 18 | } 19 | 20 | user, err := cfg.DB.GetUserByAPIKey(r.Context(), apiKey) 21 | if err != nil { 22 | respondWithError(w, http.StatusNotFound, "Couldn't get user") 23 | return 24 | } 25 | 26 | handler(w, r, user) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/queries/feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- name: GetFeedFollowsForUser :many 2 | SELECT * FROM feed_follows WHERE user_id = $1; 3 | -- 4 | 5 | -- name: CreateFeedFollow :one 6 | INSERT INTO feed_follows (id, created_at, updated_at, user_id, feed_id) 7 | VALUES ($1, $2, $3, $4, $5) 8 | RETURNING *; 9 | -- 10 | 11 | -- name: DeleteFeedFollow :exec 12 | DELETE FROM feed_follows WHERE id = $1 and user_id = $2; 13 | -- 14 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/queries/feeds.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateFeed :one 2 | INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) 3 | VALUES ($1, $2, $3, $4, $5, $6) 4 | RETURNING *; 5 | 6 | -- name: GetFeeds :many 7 | SELECT * FROM feeds; 8 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/schema/003_feeds.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feeds ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feeds; 13 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sql/schema/004_feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feed_follows ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, 7 | feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE, 8 | UNIQUE (user_id, feed_id) 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feed_follows; 13 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/8-feedfollows/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | -------------------------------------------------------------------------------- /project/9-scraper/src/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .env 3 | -------------------------------------------------------------------------------- /project/9-scraper/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootdotdev/projects/scraper 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-chi/chi v1.5.4 7 | github.com/go-chi/cors v1.2.1 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | -------------------------------------------------------------------------------- /project/9-scraper/src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 2 | github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= 3 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 4 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | -------------------------------------------------------------------------------- /project/9-scraper/src/handler_ready.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func handlerReadiness(w http.ResponseWriter, r *http.Request) { 6 | respondWithJSON(w, http.StatusOK, map[string]string{"status": "ok"}) 7 | } 8 | 9 | func handlerErr(w http.ResponseWriter, r *http.Request) { 10 | respondWithError(w, http.StatusInternalServerError, "Internal Server Error") 11 | } 12 | -------------------------------------------------------------------------------- /project/9-scraper/src/internal/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | var ErrNoAuthHeaderIncluded = errors.New("no authorization header included") 10 | 11 | // GetAPIKey - 12 | func GetAPIKey(headers http.Header) (string, error) { 13 | authHeader := headers.Get("Authorization") 14 | if authHeader == "" { 15 | return "", ErrNoAuthHeaderIncluded 16 | } 17 | splitAuth := strings.Split(authHeader, " ") 18 | if len(splitAuth) < 2 || splitAuth[0] != "ApiKey" { 19 | return "", errors.New("malformed authorization header") 20 | } 21 | 22 | return splitAuth[1], nil 23 | } 24 | -------------------------------------------------------------------------------- /project/9-scraper/src/internal/database/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/9-scraper/src/internal/database/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.17.2 4 | 5 | package database 6 | 7 | import ( 8 | "database/sql" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | ) 13 | 14 | type Feed struct { 15 | ID uuid.UUID 16 | CreatedAt time.Time 17 | UpdatedAt time.Time 18 | Name string 19 | Url string 20 | UserID uuid.UUID 21 | LastFetchedAt sql.NullTime 22 | } 23 | 24 | type FeedFollow struct { 25 | ID uuid.UUID 26 | CreatedAt time.Time 27 | UpdatedAt time.Time 28 | UserID uuid.UUID 29 | FeedID uuid.UUID 30 | } 31 | 32 | type User struct { 33 | ID uuid.UUID 34 | CreatedAt time.Time 35 | UpdatedAt time.Time 36 | Name string 37 | ApiKey string 38 | } 39 | -------------------------------------------------------------------------------- /project/9-scraper/src/json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func respondWithError(w http.ResponseWriter, code int, msg string) { 10 | if code > 499 { 11 | log.Printf("Responding with 5XX error: %s", msg) 12 | } 13 | type errorResponse struct { 14 | Error string `json:"error"` 15 | } 16 | respondWithJSON(w, code, errorResponse{ 17 | Error: msg, 18 | }) 19 | } 20 | 21 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 22 | w.Header().Set("Content-Type", "application/json") 23 | dat, err := json.Marshal(payload) 24 | if err != nil { 25 | log.Printf("Error marshalling JSON: %s", err) 26 | w.WriteHeader(500) 27 | return 28 | } 29 | w.WriteHeader(code) 30 | w.Write(dat) 31 | } 32 | -------------------------------------------------------------------------------- /project/9-scraper/src/middleware_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bootdotdev/projects/scraper/internal/auth" 7 | "github.com/bootdotdev/projects/scraper/internal/database" 8 | ) 9 | 10 | type authedHandler func(http.ResponseWriter, *http.Request, database.User) 11 | 12 | func (cfg *apiConfig) middlewareAuth(handler authedHandler) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | apiKey, err := auth.GetAPIKey(r.Header) 15 | if err != nil { 16 | respondWithError(w, http.StatusUnauthorized, "Couldn't find api key") 17 | return 18 | } 19 | 20 | user, err := cfg.DB.GetUserByAPIKey(r.Context(), apiKey) 21 | if err != nil { 22 | respondWithError(w, http.StatusNotFound, "Couldn't get user") 23 | return 24 | } 25 | 26 | handler(w, r, user) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/queries/feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- name: GetFeedFollowsForUser :many 2 | SELECT * FROM feed_follows WHERE user_id = $1; 3 | -- 4 | 5 | -- name: CreateFeedFollow :one 6 | INSERT INTO feed_follows (id, created_at, updated_at, user_id, feed_id) 7 | VALUES ($1, $2, $3, $4, $5) 8 | RETURNING *; 9 | -- 10 | 11 | -- name: DeleteFeedFollow :exec 12 | DELETE FROM feed_follows WHERE id = $1 and user_id = $2; 13 | -- 14 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/queries/feeds.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateFeed :one 2 | INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) 3 | VALUES ($1, $2, $3, $4, $5, $6) 4 | RETURNING *; 5 | 6 | -- name: GetFeeds :many 7 | SELECT * FROM feeds; 8 | 9 | -- name: GetNextFeedsToFetch :many 10 | SELECT * FROM feeds 11 | ORDER BY last_fetched_at ASC NULLS FIRST 12 | LIMIT $1; 13 | 14 | -- name: MarkFeedFetched :one 15 | UPDATE feeds 16 | SET last_fetched_at = NOW(), 17 | updated_at = NOW() 18 | WHERE id = $1 19 | RETURNING *; 20 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/queries/users.sql: -------------------------------------------------------------------------------- 1 | -- name: CreateUser :one 2 | INSERT INTO users (id, created_at, updated_at, name, api_key) 3 | VALUES ( 4 | $1, 5 | $2, 6 | $3, 7 | $4, 8 | encode(sha256(random()::text::bytea), 'hex') 9 | ) 10 | RETURNING *; 11 | 12 | -- name: GetUserByAPIKey :one 13 | SELECT * FROM users WHERE api_key = $1; 14 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/schema/001_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE users ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL 7 | ); 8 | 9 | -- +goose Down 10 | DROP TABLE users; 11 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/schema/002_users_apikey.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( 3 | encode(sha256(random()::text::bytea), 'hex') 4 | ); 5 | 6 | -- +goose Down 7 | ALTER TABLE users DROP COLUMN api_key; 8 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/schema/003_feeds.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feeds ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | name TEXT NOT NULL, 7 | url TEXT NOT NULL UNIQUE, 8 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feeds; 13 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/schema/004_feed_follows.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | CREATE TABLE feed_follows ( 3 | id UUID PRIMARY KEY, 4 | created_at TIMESTAMP NOT NULL, 5 | updated_at TIMESTAMP NOT NULL, 6 | user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, 7 | feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE, 8 | UNIQUE (user_id, feed_id) 9 | ); 10 | 11 | -- +goose Down 12 | DROP TABLE feed_follows; 13 | -------------------------------------------------------------------------------- /project/9-scraper/src/sql/schema/005_feed_lastfetched.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | ALTER TABLE feeds ADD COLUMN last_fetched_at TIMESTAMP; 3 | 4 | -- +goose Down 5 | ALTER TABLE feeds DROP COLUMN last_fetched_at; 6 | -------------------------------------------------------------------------------- /project/9-scraper/src/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | sql: 3 | - schema: "sql/schema" 4 | queries: "sql/queries" 5 | engine: "postgresql" 6 | gen: 7 | go: 8 | out: "internal/database" 9 | -------------------------------------------------------------------------------- /project/9-scraper/src/vendor/github.com/go-chi/chi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sw? 3 | .vscode 4 | -------------------------------------------------------------------------------- /project/9-scraper/src/vendor/github.com/go-chi/chi/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "**********************************************************" 3 | @echo "** chi build tool **" 4 | @echo "**********************************************************" 5 | 6 | 7 | test: 8 | go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware 9 | 10 | test-router: 11 | go test -race -v . 12 | 13 | test-middleware: 14 | go test -race -v ./middleware 15 | -------------------------------------------------------------------------------- /project/9-scraper/src/vendor/github.com/joho/godotenv/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /project/9-scraper/src/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/go-chi/chi v1.5.4 2 | ## explicit; go 1.16 3 | github.com/go-chi/chi 4 | # github.com/go-chi/cors v1.2.1 5 | ## explicit; go 1.14 6 | github.com/go-chi/cors 7 | # github.com/joho/godotenv v1.5.1 8 | ## explicit; go 1.12 9 | github.com/joho/godotenv 10 | --------------------------------------------------------------------------------