Skip to content

Commit

Permalink
refactor(frontend): Use github OAuth token
Browse files Browse the repository at this point in the history
* Refactor the whole frontend structure to work with the github oauth
* Also added tag repositories page
  • Loading branch information
Grohden committed Aug 5, 2020
1 parent a6103bb commit a505887
Show file tree
Hide file tree
Showing 39 changed files with 760 additions and 900 deletions.
30 changes: 14 additions & 16 deletions frontend/lib/api/tagger/repository_tagger_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,31 @@ abstract class RepositoryTaggerClient {
@required String baseUrl,
}) = _RepositoryTaggerClient;

/// Logs a user based on given [credential]
///
/// Returns a [token] which should be user for authenticated
/// requests
@POST('/login')
Future<String> login(@Body() UserPasswordCredential credential);

/// Registers a user on the system
///
/// Returns a confirmation string or error if the user
/// already exists in database
@POST('/register')
Future<String> register(@Body() RegisterUser user);

/// Lists a authorized user starred repositories
@GET('/repository/starred')
Future<List<SourceRepository>> starredRepos();
Future<List<SimpleRepository>> starredRepos();

/// Lists all repositories associated with a tag
@GET('/tag/{tagId}/repositories')
Future<List<SimpleRepository>> repositoriesByTag(@Path('tagId') int id);

/// Lists a authorized user repository details (with user tags)
/// given based on the given id
@GET('/repository/details/{id}')
Future<DetailedSourceRepository> detailedRepo(@Path('id') int id);
Future<DetailedRepository> detailedRepo(@Path('id') int id);

/// Add a new tag on a repository
@POST('/tag/add')
Future<UserTag> addTag(@Body() CreateTagInput input);

/// Requires oauth redirection from server
@GET('/oauth')
Future<String> oauth();

/// Checks if this app/browser has session
@GET('/has-session')
Future<bool> hasSession();

/// Removes a tag from a repository
@DELETE('/repository/{githubId}/remove-tag/{userTagId}')
Future removeTag({
Expand Down
126 changes: 46 additions & 80 deletions frontend/lib/api/tagger/repository_tagger_models.dart
Original file line number Diff line number Diff line change
@@ -1,36 +1,5 @@
part of 'repository_tagger_client.dart';

@JsonSerializable()
class UserPasswordCredential {
UserPasswordCredential({this.name, this.password});

final String name;
final String password;

factory UserPasswordCredential.fromJson(Map<String, dynamic> json) =>
_$UserPasswordCredentialFromJson(json);

Map<String, dynamic> toJson() => _$UserPasswordCredentialToJson(this);
}

@JsonSerializable()
class RegisterUser {
RegisterUser({
@required this.name,
@required this.displayName,
@required this.password,
});

final String name;
final String displayName;
final String password;

factory RegisterUser.fromJson(Map<String, dynamic> json) =>
_$RegisterUserFromJson(json);

Map<String, dynamic> toJson() => _$RegisterUserToJson(this);
}

@JsonSerializable()
class CreateTagInput {
CreateTagInput({
Expand All @@ -47,80 +16,77 @@ class CreateTagInput {
Map<String, dynamic> toJson() => _$CreateTagInputToJson(this);
}

/// Represents a detailed repository, meaning that
/// it contains user related values (tags)
@JsonSerializable()
class SourceRepository {
SourceRepository({
@required this.id,
class DetailedRepository {
DetailedRepository({
@required this.githubId,
@required this.name,
@required this.description,
@required this.url,
@required this.htmlUrl,
@required this.language,
@required this.ownerName,
@required this.stargazersCount,
@required this.forksCount,
@required this.userTags,
@required this.readmeContents,
});

final int id;
final String name;
final String description;
final String url;

@JsonKey(nullable: true)
final String language;

@JsonKey(name: 'stargazers_count')
final int stargazersCount;

factory SourceRepository.fromJson(Map<String, dynamic> json) =>
_$SourceRepositoryFromJson(json);

Map<String, dynamic> toJson() => _$SourceRepositoryToJson(this);
int githubId;
String name;
String description;
String htmlUrl;
String language;
String ownerName;
int stargazersCount;
int forksCount;
List<UserTag> userTags;
String readmeContents;

factory DetailedRepository.fromJson(Map<String, dynamic> json) =>
_$DetailedRepositoryFromJson(json);

Map<String, dynamic> toJson() => _$DetailedRepositoryToJson(this);
}

/// Represents a more simpler repository, meaning
/// that it has the necessary data to be used
/// on a list
@JsonSerializable()
class DetailedSourceRepository {
DetailedSourceRepository({
@required this.id,
class SimpleRepository {
SimpleRepository({
@required this.githubId,
@required this.name,
@required this.description,
@required this.url,
@required this.language,
@required this.ownerName,
@required this.stargazersCount,
@required this.userTags,
@required this.forksCount,
@required this.readmeUrl,
});

final int id;
final String name;
final String description;
final String url;

@JsonKey(name: 'user_tags', defaultValue: [])
final List<UserTag> userTags;

@JsonKey(nullable: true)
final String language;

@JsonKey(name: 'stargazers_count')
final int stargazersCount;
int githubId;
String name;
String description;
String url;
String language;
String ownerName;
int stargazersCount;
int forksCount;

@JsonKey(name: 'forks_count')
final int forksCount;
factory SimpleRepository.fromJson(Map<String, dynamic> json) =>
_$SimpleRepositoryFromJson(json);

@JsonKey(name: 'readme_url')
final String readmeUrl;

factory DetailedSourceRepository.fromJson(Map<String, dynamic> json) =>
_$DetailedSourceRepositoryFromJson(json);

Map<String, dynamic> toJson() => _$DetailedSourceRepositoryToJson(this);
Map<String, dynamic> toJson() => _$SimpleRepositoryToJson(this);
}

@JsonSerializable()
class UserTag {
UserTag({@required this.id, @required this.name});
UserTag({@required this.tagId, @required this.tagName});

final int id;
final String name;
final int tagId;
final String tagName;

factory UserTag.fromJson(Map<String, dynamic> json) =>
_$UserTagFromJson(json);
Expand Down
109 changes: 64 additions & 45 deletions frontend/lib/external/taggable/tagging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class FlutterTagging<T> extends StatefulWidget {

/// The configuration of the [TextField] that the [FlutterTagging]
/// widget displays.
final TextFieldConfiguration textFieldConfiguration;
final TextFieldConfiguration<T> textFieldConfiguration;

/// Called with the search pattern to get the search suggestions.
///
Expand Down Expand Up @@ -149,6 +149,9 @@ class FlutterTagging<T> extends StatefulWidget {
/// Initial items
final List<T> initialItems;

/// on chip tap callback
final Function(T) onChipTap;

/// Creates a [FlutterTagging] widget.
const FlutterTagging({
@required this.initialItems,
Expand All @@ -174,6 +177,7 @@ class FlutterTagging<T> extends StatefulWidget {
this.animationStart = 0.25,
this.onAdded,
this.onRemoved,
this.onChipTap,
}) : assert(initialItems != null),
assert(areObjectsEqual != null),
assert(findSuggestions != null),
Expand All @@ -186,6 +190,7 @@ class FlutterTagging<T> extends StatefulWidget {

class _FlutterTaggingState<T> extends State<FlutterTagging<T>> {
TextEditingController _textController;
final _boxController = SuggestionsBoxController();
FocusNode _focusNode;
T _additionItem;

Expand Down Expand Up @@ -214,6 +219,7 @@ class _FlutterTaggingState<T> extends State<FlutterTagging<T>> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
TypeAheadField<T>(
suggestionsBoxController: _boxController,
getImmediateSuggestions: widget.enableImmediateSuggestion,
debounceDuration: widget.debounceDuration,
hideOnEmpty: widget.hideOnEmpty,
Expand All @@ -228,7 +234,6 @@ class _FlutterTaggingState<T> extends State<FlutterTagging<T>> {
keepSuggestionsOnLoading: suggestionsConf.keepSuggestionsOnLoading,
keepSuggestionsOnSuggestionSelected:
suggestionsConf.keepSuggestionsOnSuggestionSelected,
suggestionsBoxController: suggestionsConf.suggestionsBoxController,
suggestionsBoxDecoration: suggestionsConf.suggestionsBoxDecoration,
suggestionsBoxVerticalOffset:
suggestionsConf.suggestionsBoxVerticalOffset,
Expand All @@ -243,24 +248,24 @@ class _FlutterTaggingState<T> extends State<FlutterTagging<T>> {
},
noItemsFoundBuilder: widget.emptyBuilder,
textFieldConfiguration: widget.textFieldConfiguration.copyWith(
focusNode: _focusNode,
controller: _textController,
enabled: widget.textFieldConfiguration.enabled,
// Typeahead doesn't annotate public copy with
// so we have to cast here, yay! :D
) as TextFieldConfiguration,
focusNode: _focusNode,
controller: _textController,
onEditingComplete: () {
final currentText = _textController.text;
if (currentText.isEmpty) {
return;
}

final item = widget.additionCallback(currentText);
_onSuggestionSelected(item);
_boxController.close();
}
// Typeahead doesn't annotate public copy with
// so we have to cast here, yay! :D
) as TextFieldConfiguration<T>,
suggestionsCallback: _suggestionsCallback,
itemBuilder: _buildItem,
onSuggestionSelected: (suggestion) async {
final added = await widget.onAdded?.call(suggestion);

if(added != null){
setState(() {
widget.initialItems.add(suggestion);
});
}
_textController.clear();
},
onSuggestionSelected: _onSuggestionSelected,
),
Wrap(
alignment: widget.wrapConfiguration.alignment,
Expand All @@ -273,42 +278,56 @@ class _FlutterTaggingState<T> extends State<FlutterTagging<T>> {
verticalDirection: widget.wrapConfiguration.verticalDirection,
children: widget.initialItems.map<Widget>((item) {
var conf = widget.configureChip(item);
return Chip(
label: conf.label,
shape: conf.shape,
avatar: conf.avatar,
backgroundColor: conf.backgroundColor,
clipBehavior: conf.clipBehavior,
deleteButtonTooltipMessage: conf.deleteButtonTooltipMessage,
deleteIcon: conf.deleteIcon,
deleteIconColor: conf.deleteIconColor,
elevation: conf.elevation,
labelPadding: conf.labelPadding,
labelStyle: conf.labelStyle,
materialTapTargetSize: conf.materialTapTargetSize,
padding: conf.padding,
shadowColor: conf.shadowColor,
onDeleted: () {
final removed = widget.initialItems.firstWhere(
(element) => widget.areObjectsEqual(element, item),
orElse: () => null,
);

setState(() {
widget.initialItems.removeWhere((element) {
return widget.areObjectsEqual(element, item);
return GestureDetector(
onTap: () => widget.onChipTap?.call(item),
child: Chip(
label: conf.label,
shape: conf.shape,
avatar: conf.avatar,
backgroundColor: conf.backgroundColor,
clipBehavior: conf.clipBehavior,
deleteButtonTooltipMessage: conf.deleteButtonTooltipMessage,
deleteIcon: conf.deleteIcon,
deleteIconColor: conf.deleteIconColor,
elevation: conf.elevation,
labelPadding: conf.labelPadding,
labelStyle: conf.labelStyle,
materialTapTargetSize: conf.materialTapTargetSize,
padding: conf.padding,
shadowColor: conf.shadowColor,
onDeleted: () {
final removed = widget.initialItems.firstWhere(
(element) => widget.areObjectsEqual(element, item),
orElse: () => null,
);

setState(() {
widget.initialItems.removeWhere((element) {
return widget.areObjectsEqual(element, item);
});
});
});

widget.onRemoved?.call(removed);
},
widget.onRemoved?.call(removed);
},
),
);
}).toList(),
),
],
);
}

void _onSuggestionSelected(T suggestion) async {
final added = await widget.onAdded?.call(suggestion);

if (added != null) {
setState(() {
widget.initialItems.add(added);
});
}
_textController.clear();
}

FutureOr<List<T>> _suggestionsCallback(String query) async {
var suggestions = (await widget.findSuggestions(query))
// FIX: use toList to avoid changing original one.
Expand Down
Loading

0 comments on commit a505887

Please sign in to comment.