diff --git a/README.md b/README.md index d9c26e9..8845997 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ This project is a mobile application for hackers, organizers, mentors, sponsors We had started using an inhouse hybrid mobile application to keep track of analytics to get a better idea of how certain aspects of the hackathon were running such as food consumption and optimization for checkin. This project expanded into a public native mobile application so hackers had easier access to their QR code as well as organizers with their scanners. Additional information of the hackathon were incorporated so that everyone would be able to stay up to date on events that are happeneing wherever they may be in the venue. +## Style Guide +Coming Soon... +We'll be using LINTER for Dart (https://github.com/dart-lang/linter) + ## Installation Guide ### For Architects @@ -33,6 +37,13 @@ To learn about Flutter App Development: - [Online documentation: (https://flutter.io/docs], which offers tutorials, samples, guidance on mobile development, and a full API reference. +### Running tests +1. also have command line dart installed +2. setup test users and use the test endpoint in hackru-service +3. `export LCS_USER=""` for LCS_USER, LCS_PASSWORD, LCS_USER2, LCS_PASSWORD2 + - lcs user should have the director role +4. `cd lib && dart test.dart` + ### For Users Coming Soon... @@ -49,11 +60,10 @@ List of features goes here... ### Needs To Be Done 1) Write a String Parser for Announcement Messages from Slack (we need to remove emojies and user mentions) -2) QR Code Scanner UI is working, but need to connect with BackEnd (LCS) -2) QR Code Gen UI is working, but need to connect with BackEnd (LCS) -3) LogIn/SignUp UI is working but need to connect with BackEnd (LCS) +2) QR Code Scanner UI is working, but need to connect with BackEnd (LCS) (Code Exists) +2) QR Code Gen UI is working, but need to connect with BackEnd (LCS) (Code Exists) +3) LogIn/SignUp UI is working but need to connect with BackEnd (LCS) (Code Exists) 4) "About Page" (which will include Flutter App Dev Team Members, HackRU Rnd Reference, and about the app) -5) "Help/Tool Page" (hoping to add buttons which will redirect to inApp webview for "HelpQ", "HackRU Website", "Team Builder", and whatever hackers need in one click) ## Links to Further docs TBA diff --git a/lib/hackru_service.dart b/lib/hackru_service.dart index 132299a..003a1f0 100644 --- a/lib/hackru_service.dart +++ b/lib/hackru_service.dart @@ -1,7 +1,9 @@ import 'package:http/http.dart' as http; import 'dart:convert'; +import 'package:HackRU/models.dart'; -const _lcsUrl = 'https://7c5l6v7ip3.execute-api.us-west-2.amazonaws.com/lcs-test'; +const _lcsUrl = 'https://7c5l6v7ip3.execute-api.us-west-2.amazonaws.com/lcs-test'; // prod +//const _lcsUrl = 'https://7c5l6v7ip3.execute-api.us-west-2.amazonaws.com/lcs-test'; // test const _miscUrl = 'http://hackru-misc.s3-website-us-west-2.amazonaws.com'; var client = new http.Client(); @@ -10,16 +12,35 @@ Future getMisc(String endpoint) { return client.get(_miscUrl + endpoint); } -Future getLcs(String endpoint) { - return client.get(_lcsUrl + endpoint); +String toParam(LcsCredential credential) { + var param = ""; + if (credential != null) { + if (credential.isExpired()) { + throw CredentialExpired(); + } + param = "?token="+credential.token; + } + return param; } -Future postLcs(String endpoint, dynamic body) { +Future getLcs(String endpoint, [LcsCredential credential]) { + return client.get(_lcsUrl + endpoint + toParam(credential)); +} + +Future postLcs(String endpoint, dynamic body, [LcsCredential credential]) async { var encodedBody = jsonEncode(body); - return client.post(_lcsUrl + endpoint, + var result = await client.post(_lcsUrl + endpoint + toParam(credential), headers: {"content-Type": "applicationi/json"}, body: encodedBody ); + var decoded = jsonDecode(result.body); + if(decoded["statusCode"] != result.statusCode) { + print(decoded); + print("!!!!!!!!!!!!WARNING"); + print("body and container status code dissagree actual ${result.statusCode} body: ${decoded['statusCode']}"); + print(endpoint); + } + return result; } // misc functions @@ -28,17 +49,14 @@ Future> sitemap() async { return await response.body.split("\n"); } -class HelpResource { - final String name; - final String description; - final String url; - - HelpResource(this.name, this.description, this.url); +Future> events() async { + var response = await getMisc("/events.txt"); + return await response.body.split("\n"); +} - HelpResource.fromJson(Map json) - : name = json['name'], - description = json['desc'], - url = json['url']; +Future labelUrl() async { + var response = await getMisc("/label-url.txt"); + return response.body; } Future> helpResources() async { @@ -50,3 +68,64 @@ Future> helpResources() async { } // lcs functions + +// /authorize can give wrong status codes +Future login(String email, String password) async { + var result = await postLcs("/authorize", { + "email": email, + "password": password, + }); + var body = jsonDecode(result.body); + // quirk with lcs where it puts the actual result as a string + // inside the normal body + if (body["statusCode"] == 200) { + var auth = jsonDecode(body["body"])["auth"]; + return LcsCredential.fromJson(auth); + } else if (body["statusCode"] == 403) { + throw LcsLoginFailed(); + } else { + throw LcsError(result); + } +} + +Future getUser(LcsCredential credential, [String targetEmail]) async { + if (targetEmail == null) { + targetEmail = credential.email; + } + var result = await postLcs("/read", { + "email": credential.email, + "token": credential.token, + "query": {"email": targetEmail} + }, credential); + if (result.statusCode == 200) { + var users = jsonDecode(result.body)["body"]; + if (users.length < 1) { + throw NoSuchUser(); + } + return User.fromJson(users[0]); + } else { + throw LcsError(result); + } +} + +// /update can give wrong status codes +// check if the user credential belongs to is role.director first. or else it will break :( +void updateUserDayOf(LcsCredential credential, User user, String event) async { + print(event); + var result = await postLcs("/update", { + "updates": {"\$set":{"day_of.$event": true}}, + "user_email": user.email, + "auth_email": credential.email, + "auth": credential.token, + }, credential); + + var decoded = jsonDecode(result.body); + if (decoded["statusCode"] == 400) { + throw UpdateError(decoded["body"]); + } else if (decoded["statusCode"] == 403) { + // BROKEN BECAUSE LCS + throw PermissionError(); + } else if (decoded["statusCode"] != 200){ + throw LcsError(result); + } +} diff --git a/lib/models.dart b/lib/models.dart new file mode 100644 index 0000000..1eb92ad --- /dev/null +++ b/lib/models.dart @@ -0,0 +1,122 @@ +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class LcsCredential { + final String email; + final String token; + final DateTime expiration; + + LcsCredential(this.email, this.token, this.expiration); + + @override + String toString() { + return "LcsCredentail{email: ${this.email}, " + + "token: ${this.token}, " + + "expiration: ${this.expiration}}"; + } + + bool isExpired() { + return this.expiration.isBefore(DateTime.now()); + } + + LcsCredential.fromJson(Map json) + : email = json["email"], + token = json["token"], + expiration = DateTime.parse(json["valid_until"]); +} + +class HelpResource { + final String name; + final String description; + final String url; + + HelpResource(this.name, this.description, this.url); + + @override + String toString() { + return "HelpResource{name: ${this.name}, " + + "description: ${this.description}, " + + "url: ${this.url}}"; + } + + HelpResource.fromJson(Map json) + : name = json["name"], + description = json['desc'], + url = json["url"]; +} + +class User { + final String name; + final String email; + final Map role; // role.director, admin, organizer + final Map dayOf; // this should always be a bool really + + User(this.name, this.email, this.dayOf, this.role); + + @override + String toString() { + return "User{name: ${this.name}, " + + "email: ${this.email}, " + + "role: ${this.role}, " + + "dayOf: ${this.dayOf}"; + } + + // check if a hacker has already attended an event + bool alreadyDid(String event) { + if (!this.dayOf.containsKey(event)) { + return false; + } else { + return this.dayOf[event]; + } + } + + User.fromJson(Map json) + : name = (json["first_name"] ?? "") + " " + (json["last_name"] ?? ""), + email = json["email"], + dayOf = json["day_of"], + role = json["role"]; +} + +class LcsError implements Exception { + String lcsError; + int code; + LcsError(http.Response res) { + this.code = res.statusCode; + if (res.statusCode >= 500) { + this.lcsError = "internal error with lcs"; + } else { + var body = jsonDecode(res.body); + this.code = body["statusCode"]; + this.lcsError = body["body"]; + } + } + String errorMessage() => "LCS error $code: $lcsError"; + String toString() => errorMessage(); +} + +class LcsLoginFailed implements Exception { + String errorMessage() => "bad username or password"; + String toString() => errorMessage(); +} + +class CredentialExpired implements Exception { + String errorMessage() => "credential expired user must log in"; + String toString() => errorMessage(); +} + +class NoSuchUser implements Exception { + String errorMessage() => "no user with that email"; + String toString() => errorMessage(); +} + +class PermissionError implements Exception { + String errorMessage() => "you don't have permission to do that"; + String toString() => errorMessage(); +} + +class UpdateError implements Exception { + final String lcsMessage; + UpdateError(this.lcsMessage); + String errorMessage() => "Failed to update user: $lcsMessage"; + String toString() => errorMessage(); +} diff --git a/lib/screens/help.dart b/lib/screens/help.dart index 9088e4d..2dea7da 100644 --- a/lib/screens/help.dart +++ b/lib/screens/help.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:HackRU/colors.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:HackRU/hackru_service.dart'; +import 'package:HackRU/models.dart'; class HelpButton extends StatelessWidget { HelpButton({@required this.resource}); diff --git a/lib/test.dart b/lib/test.dart new file mode 100644 index 0000000..0324d03 --- /dev/null +++ b/lib/test.dart @@ -0,0 +1,112 @@ +import 'package:HackRU/hackru_service.dart'; +import 'package:HackRU/models.dart'; +import 'dart:io' show Platform; + +var env = Platform.environment; + +void testHelpResources() async { + var result = await helpResources(); + print("test helpResources"); + result.forEach((resource) { + print(resource); + }); +} + +void testSitemap() async { + var result = await sitemap(); + print("test sitemap"); + print(result); +} + +void testEvents() async { + var result = await events(); + print("test events"); + print(result); +} + +void testLabelUrl() async { + var result = await labelUrl(); + print("test label-url"); + print(result); +} + +void testExpired() { + print("test catch expired credential"); + var l = LcsCredential("test", "account", DateTime.now()); + assert(l.isExpired()); +} + +void testLogin() async { + try { + await login("bogus", "login"); + print("failed to catch bad login"); + } on LcsLoginFailed catch (e) { + print("caught failed login"); + } + var l = await login(env["LCS_USER"], env["LCS_PASSWORD"]); + assert(!l.isExpired()); +} + +void testPostLcsExpired() async { + print("test for postLcs to catch expired credentials"); + var cred = LcsCredential("Bogus", "Cred", DateTime.now()); + try { + await postLcs("/read", {}, cred); + assert(false); // should have thrown CredentialExpired + } on CredentialExpired catch(e) { + print("succesfully caught expired credential"); + } +} + +void testGetUser() async { + var cred = await login(env["LCS_USER"], env["LCS_PASSWORD"]); + var user = await getUser(cred); + print("test get user"); + print(user); +} +void testOtherUser() async { + var cred = await login(env["LCS_USER"], env["LCS_PASSWORD"]); + var user = await getUser(cred, "test1@regist.er"); + try { + var baduser = await getUser(cred, "fail@email.com"); + assert(false); + } on NoSuchUser catch(error) { + print("successfuly caught attempt to get nonexistent user"); + } + print("test get a different user"); + print(user); +} + +void testUpdateDayOf() async { + var cred = await login(env["LCS_USER"], env["LCS_PASSWORD"]); + var user = await getUser(cred, env["LCS_USER2"]); + await updateUserDayOf(cred, user, "fake_event${DateTime.now().millisecondsSinceEpoch}"); + var user2 = await getUser(cred, env["LCS_USER2"]); + print("test update user day_of"); + print(user); + print(user2); +} +/* +// !%$#@*&!!!! +void testUpdateDayOfPerm() async { + var cred = await login(env["LCS_USER2"], env["LCS_PASSWORD2"]); + var user = await getUser(cred, "LCS_USER"); + await updateUserDayOf(cred, user, "fake_event${DateTime.now().millisecondsSinceEpoch}"); + var user2 = await getUser(cred, "LCS_USER"); + print("test update user day_of"); + print(user); + print(user2); +}*/ + +void main() async { + testHelpResources(); + testSitemap(); + testEvents(); + testLabelUrl(); + testExpired(); + testLogin(); + testPostLcsExpired(); + testGetUser(); + testOtherUser(); + testUpdateDayOf(); +}