From 72d33c21caa33e415920f2d860f829bca9f3b1e3 Mon Sep 17 00:00:00 2001 From: Nathan Anderson Date: Wed, 9 Oct 2024 19:16:35 -0600 Subject: [PATCH] Updated readme with instructions --- README.md | 84 ++++++++++--------------- new_backend/lib/shrink_ray.dart | 2 +- new_backend/routes/index.dart | 8 ++- new_backend/routes/u/[urlPath].dart | 4 +- new_backend/test/routes/index_test.dart | 10 +-- 5 files changed, 43 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index d621791..abbaace 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,46 @@ -# URL Shortener Take-Home Exercise (Fullstack) - -## Instructions - -Your task is to create a URL shortener web application (similar to [bitly](https://bitly.com/) or [TinyURL](https://tinyurl.com/)). This exercise is intentionally open-ended, and you are welcome to implement your solution using the language and tech stack of your choice. If you are familiar with React & Next.js, please use those for your submission. The core functionality of the application should be expressed through your own original code. - -You should aim to spend no more than 2 hours on this project. If you don't complete everything in 2 hours, please submit what you have - we value your time and want to see your prioritization skills. - -### Application Description - -At the root path (e.g., http://localhost:8080/), a user should be presented with a form that allows them to input a URL. When a user submits that form, it should convert the input URL to a shortened version and present that to the user. - -The shortened URL should be in the format: http://localhost:8080/{slug}, where `{slug}` is a unique identifier for the original URL. - -When a user navigates to the shortened URL, they should be redirected to the original URL that was used to generate this shortened URL. - -### Minimum Requirements - -* Format and method of generating slugs for shortened URLs are up to you -* Shortened URLs do not need to persist across server shutdown/startup (i.e., setting up a DB isn't necessary - server memory should suffice) -* Only allow valid http(s) URLs - -If you have additional time, consider spending it on testing or UI improvements as opposed to supplemental features. - -## Evaluation Criteria - -We will be evaluating your submission based on the following: - -1. Functionality: Does the application work as described? -2. Code quality: Is the code clean, well-organized, and following best practices? -3. Error handling: How does the application handle invalid inputs or errors? -4. Technical choices: Are the chosen technologies appropriate for the task? -5. Documentation: Is the code well-commented and the README clear? - -## Deliverables - -Please fill out the sections below in the _README.md_ of your project and submit according to the instructions you received with this project. Your code can be sent as a zip file or a link to a repository containing your project. - ---- +# Url Shortener ## Implementation Details - +I used Dart/Flutter to write the entire stack. Frontend is flutter compiled to web. Backend uses the [dart_frog](https://dartfrog.vgv.dev/) framework. + +Architecture is a simple server that serves the static frontend files on `/` and requests to create shortened urls as well as the redirects. + +Dart frog does file-based routing similar to Next.js. However, I was unable to get the static content served on `/` and get shortened urls to route to `/[slug]` so I added them to a sub-route `/u/[slug]`. ## How to Run - +### Running with Docker (Simpler) + +With docker installed, `cd` into the `new_backend/build/` directory where the `Dockerfile` is located. Build the docker image with + +```sh +docker build -t ./ +``` + +Once built, run it with: +```sh +docker run -d -p 8080:8080 +``` + +The container port is hard-coded, so you have to use the internal port 8080, the host can be whichever port. + +### Running the local dev server + +- [Install dart](https://dart.dev/get-dart) +- [Install dart_frog](https://dartfrog.vgv.dev/docs/overview) + +Then run the backend with: +```sh +cd new_backend/ && dart pub get && dart_frog dev +``` ## Testing - +I wrote some unit tests to get the url shortening to work, and everything else was tested manually. I also include some asserts in the code to verify the various assumptions I made in the code. ## Tools Used - +I used various documentation sites, like the one for the dart_frog web framework, as well as the [MDN docs for response codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). ---- - -Good luck, and we look forward to reviewing your submission! +I discovered from asking an LLM that I could use the redirection status codes to do the url resolving, but otherwise referenced the docs to decide to use 308. diff --git a/new_backend/lib/shrink_ray.dart b/new_backend/lib/shrink_ray.dart index f3553f1..7762090 100644 --- a/new_backend/lib/shrink_ray.dart +++ b/new_backend/lib/shrink_ray.dart @@ -4,7 +4,7 @@ enum Status { ok, error } class ShrinkRay { static const shrinkLength = 6; - static const String shrinkChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._'; + static const String shrinkChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; static ({Status status, String content}) shrinkUrl(String urlStr) { try { diff --git a/new_backend/routes/index.dart b/new_backend/routes/index.dart index fe7e90f..b3f3fcb 100644 --- a/new_backend/routes/index.dart +++ b/new_backend/routes/index.dart @@ -21,7 +21,6 @@ Future onRequest(RequestContext context) async { if (url == null) { return Response(statusCode: 401, body: 'Missing url'); } - await Future.delayed(const Duration(seconds: 2)); final res = ShrinkRay.shrinkUrl(url); switch (res.status) { case Status.ok: @@ -40,6 +39,13 @@ Future onRequest(RequestContext context) async { return Response(statusCode: 500, body: 'Unexpected error occurred'); } case HttpMethod.get: + // final maybeUrl = shrunkUrls[urlPath]; + // switch (maybeUrl) { + // case null: + // return Response(statusCode: 404); + // default: + // return Response(statusCode: 308, headers: {'Location': maybeUrl}); + // } final file = File(path.join(Directory.current.path, 'public', 'index.html')); if (!file.existsSync()) { return Response(body: 'Index Not found'); diff --git a/new_backend/routes/u/[urlPath].dart b/new_backend/routes/u/[urlPath].dart index ebf6e62..5bd5367 100644 --- a/new_backend/routes/u/[urlPath].dart +++ b/new_backend/routes/u/[urlPath].dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:dart_frog/dart_frog.dart'; import 'package:new_backend/shrink_ray.dart'; @@ -18,7 +16,7 @@ Future onRequest(RequestContext context, String urlPath) async { case null: return Response(statusCode: 404); default: - return Response(statusCode: 302, headers: {'Location': maybeUrl}); + return Response(statusCode: 308, headers: {'Location': maybeUrl}); } } } diff --git a/new_backend/test/routes/index_test.dart b/new_backend/test/routes/index_test.dart index b5976e5..2dcf823 100644 --- a/new_backend/test/routes/index_test.dart +++ b/new_backend/test/routes/index_test.dart @@ -8,14 +8,6 @@ class _MockRequestContext extends Mock implements RequestContext {} void main() { group('GET /', () { - test('responds with a 200 and "Welcome to Dart Frog!".', () { - // final context = _MockRequestContext(); - // final response = route.onRequest(context); - // expect(response.statusCode, equals(HttpStatus.ok)); - // expect( - // response.body(), - // completion(equals('Welcome to Dart Frog!')), - // ); - }); + test('responds with a 200 and "Welcome to Dart Frog!".', () {}); }); }