frecon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f815f2ffe36eae99a451ce057c3f2bc41a355783
4
+ data.tar.gz: 0b415f99c4fdc44bc23236bd259cddec01bd505b
5
+ SHA512:
6
+ metadata.gz: 86b2d39602ae88bce2c4c10312ac8f9f91f29170b8774104e954d7300bb6e56cecf97ee3d72ec6c5c7c656b7c9c100227efa72cf5df74849a1f944b176c13fb7
7
+ data.tar.gz: 0ce13da7ae08cffc748ad6e4ca3baa30e8a0606dfc98b5bc4131b7f59a2a24edc730540a028a2797ae780aed853d03fa87e9b108878465f1eac37a63611b7a89
data/bin/frecon ADDED
@@ -0,0 +1,13 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # Allow running locally.
4
+ lib_directory = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
5
+ $LOAD_PATH.unshift(lib_directory) unless $LOAD_PATH.include?(lib_directory)
6
+
7
+ require "frecon"
8
+
9
+ unless ARGV.include?("c") || ARGV.include?("console")
10
+ FReCon::Server.start
11
+ else
12
+ FReCon::Console.start
13
+ end
@@ -0,0 +1,3 @@
1
+ class Object
2
+ alias_method :is_an?, :is_a?
3
+ end
@@ -0,0 +1,4 @@
1
+ module FReCon
2
+ ENVIRONMENT = :development
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,2 @@
1
+ require "frecon/base/object"
2
+ require "frecon/base/variables"
@@ -0,0 +1,28 @@
1
+ require "frecon/database"
2
+ require "frecon/server"
3
+
4
+ module FReCon
5
+ class Console
6
+ def self.start
7
+ Database.setup
8
+
9
+ # Use pry if it is installed.
10
+ # Use the context of the FReCon module;
11
+ # this allows for writing "Team" instead of "FReCon::Team".
12
+ begin
13
+ require "pry"
14
+
15
+ FReCon.pry
16
+ rescue LoadError
17
+ require "irb"
18
+
19
+ IRB.setup nil
20
+ IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
21
+
22
+ require "irb/ext/multi-irb"
23
+
24
+ IRB.irb nil, FReCon
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,129 @@
1
+ require "frecon/base"
2
+
3
+ module FReCon
4
+ class Controller
5
+ def self.model_name
6
+ # Removes the namespace "FReCon::" and "Controller" from
7
+ # the class name, then singularizes the result.
8
+ self.name.gsub(/FReCon::|Controller\Z/, "").singularize
9
+ end
10
+
11
+ def self.model
12
+ # Removes the trailing "Controller" from the class name,
13
+ # singularizes the result, and turns it into the class.
14
+ self.name.gsub(/Controller\Z/, "").singularize.constantize
15
+ end
16
+
17
+ # The 404 error message.
18
+ def self.could_not_find(value, attribute = "id", model = model_name.downcase)
19
+ "Could not find #{model_name.downcase} of #{attribute} #{value}!"
20
+ end
21
+
22
+ # Processes a POST/PUT request and returns the post data.
23
+ def self.process_request(request)
24
+ # Rewind the request body (an IO object)
25
+ # in case someone else has already played
26
+ # through it.
27
+ request.body.rewind
28
+
29
+ begin
30
+ # Parse the POST data as a JSON hash
31
+ # (because that's what it is)
32
+ post_data = JSON.parse(request.body.read)
33
+ rescue JSON::ParserError => e
34
+ # If we have malformed JSON (JSON::ParserError is
35
+ # raised), escape out of the function.
36
+ return [400, ErrorFormatter.format(e.message)]
37
+ end
38
+
39
+ post_data.is_an?(Array) ? [422, ErrorFormatter.format("Must pass a JSON object!")] : post_data
40
+ end
41
+
42
+ def self.create(request, params)
43
+ post_data = process_request request
44
+ return post_data if post_data.is_an?(Array)
45
+
46
+ @model = model.new
47
+ @model.attributes = post_data
48
+
49
+ if @model.save
50
+ [201, @model.to_json]
51
+ else
52
+ [422, ErrorFormatter.format(@model.errors.full_messages)]
53
+ end
54
+ end
55
+
56
+ def self.update(request, params)
57
+ return [400, "Must supply a #{model_name.downcase}!"] unless params[:id]
58
+
59
+ post_data = process_request request
60
+ return post_data if post_data.is_an?(Array)
61
+
62
+ @model = model.find params[:id]
63
+
64
+ return [404, ErrorFormatter.format(could_not_find(params[:id]))] unless @model
65
+
66
+ if @model.update_attributes(post_data)
67
+ @model.to_json
68
+ else
69
+ [422, ErrorFormatter.format(@model.errors.full_messages)]
70
+ end
71
+ end
72
+
73
+ def self.delete(params)
74
+ @model = model.find params[:id]
75
+
76
+ if @model
77
+ if @model.destroy
78
+ 204
79
+ else
80
+ [422, ErrorFormatter.format(@model.errors.full_messages)]
81
+ end
82
+ else
83
+ [404, ErrorFormatter.format(could_not_find(params[:id]))]
84
+ end
85
+ end
86
+
87
+ def self.show(params)
88
+ @model = model.find params[:id]
89
+
90
+ if @model
91
+ @model.to_json
92
+ else
93
+ [404, ErrorFormatter.format(could_not_find(params[:id]))]
94
+ end
95
+ end
96
+
97
+ def self.index(params)
98
+ params.delete("_")
99
+
100
+ @models = params.empty? ? model.all : model.where(params)
101
+
102
+ @models.to_json
103
+ end
104
+
105
+ def self.show_attribute(params, attribute)
106
+ @model = model.find params[:id]
107
+
108
+ if @model
109
+ @model.send(attribute).to_json
110
+ else
111
+ [404, ErrorFormatter.format(could_not_find(params[:id]))]
112
+ end
113
+ end
114
+
115
+ def self.team_number_to_team_id(post_data)
116
+ if post_data["team_number"] && !post_data["team_id"]
117
+ unless (team = Team.number post_data["team_number"]).nil?
118
+ post_data["team_id"] = team.id
119
+
120
+ post_data.delete("team_number")
121
+
122
+ post_data
123
+ else
124
+ return [404, ErrorFormatter.format(could_not_find(post_data["team_number"], "number", "team"))]
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,18 @@
1
+ require "json"
2
+ require "frecon/models"
3
+
4
+ module FReCon
5
+ class CompetitionsController < Controller
6
+ def self.teams(params)
7
+ show_attribute params, :teams
8
+ end
9
+
10
+ def self.matches(params)
11
+ show_attribute params, :matches
12
+ end
13
+
14
+ def self.records(params)
15
+ show_attribute params, :records
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require "json"
2
+ require "frecon/models"
3
+
4
+ module FReCon
5
+ class DumpController
6
+ def self.full(params)
7
+ competitions = Competition.all
8
+ teams = Team.all
9
+ matches = Match.all
10
+ records = Record.all
11
+
12
+ {
13
+ "competitions" => competitions,
14
+ "teams" => teams,
15
+ "matches" => matches,
16
+ "records" => records
17
+ }.to_json
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ require "json"
2
+ require "frecon/models"
3
+
4
+ module FReCon
5
+ class MatchesController < Controller
6
+ def self.competition(params)
7
+ show_attribute params, :competition
8
+ end
9
+
10
+ def self.records(params)
11
+ show_attribute params, :records
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ module FReCon
2
+ class ParticipationsController < Controller
3
+ def self.create(request, params)
4
+ post_data = process_request request
5
+ return post_data if post_data.is_an?(Array)
6
+
7
+ # Convert team number to team_id.
8
+ post_data = team_number_to_team_id(post_data)
9
+
10
+ @model = model.new
11
+ @model.attributes = post_data
12
+
13
+ if @model.save
14
+ [201, @model.to_json]
15
+ else
16
+ [422, ErrorFormatter.format(@model.errors.full_messages)]
17
+ end
18
+ end
19
+
20
+ def self.competition(params)
21
+ show_attribute params, :competition
22
+ end
23
+
24
+ def self.team(params)
25
+ show_attribute params, :team
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ require "json"
2
+ require "frecon/base"
3
+ require "frecon/models"
4
+
5
+ module FReCon
6
+ class RecordsController < Controller
7
+ def self.create(request, params)
8
+ post_data = process_request request
9
+ return post_data if post_data.is_an?(Array)
10
+
11
+ # Change special post_data attributes.
12
+ # Convert team number to team id.
13
+ post_data = team_number_to_team_id(post_data)
14
+
15
+ # Convert match number and competition name to match id.
16
+ if post_data["match_number"] && !post_data["match_id"]
17
+ if post_data["competition_name"] && (competition = Competition.find_by name: post_data["competition_name"])
18
+ # Try to set the match to the already existing match.
19
+ match = competition.matches.find_by number: post_data["match_number"]
20
+
21
+ # Create the match if necessary.
22
+ match ||= Match.create(number: post_data["match_number"], competition_id: competition.id)
23
+
24
+ post_data["match_id"] = match.id
25
+
26
+ post_data.delete("match_number")
27
+ post_data.delete("competition_name")
28
+ elsif post_data["competition"] && post_data["competition"]["_id"] && post_data["competition"]["_id"]["$oid"] && (competition = Competition.find_by(id: post_data["competition"]["_id"]["$oid"]))
29
+ # Try to set the match to the already existing match.
30
+ match = competition.matches.find_by number: post_data["match_number"]
31
+
32
+ # Create the match if necessary.
33
+ match ||= Match.create(number: post_data["match_number"], competition_id: competition.id)
34
+
35
+ post_data["match_id"] = match.id
36
+
37
+ post_data.delete("match_number")
38
+ post_data.delete("competition")
39
+ else
40
+ return [422, ErrorFormatter.format("A current competition is not set. Please set it.")]
41
+ end
42
+ end
43
+
44
+ @record = Record.new
45
+ @record.attributes = post_data
46
+
47
+ if @record.save
48
+ # Use to_json for now; we can filter it later.
49
+ [201, @record.to_json]
50
+ else
51
+ [422, ErrorFormatter.format(@record.errors.full_messages)]
52
+ end
53
+ end
54
+
55
+ def self.competition(params)
56
+ show_attribute params, :competition
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,14 @@
1
+ require "json"
2
+ require "frecon/models/robot"
3
+
4
+ module FReCon
5
+ class RobotsController < Controller
6
+ def self.competition(params)
7
+ show_attribute params, :competition
8
+ end
9
+
10
+ def self.team(params)
11
+ show_attribute params, :team
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,112 @@
1
+ require "json"
2
+ require "frecon/base"
3
+ require "frecon/models"
4
+
5
+ module FReCon
6
+ class TeamsController < Controller
7
+ def self.create(request, params)
8
+ post_data = process_request request
9
+ return post_data if post_data.is_an?(Array)
10
+
11
+ @team = Team.new
12
+ @team.attributes = post_data
13
+
14
+ if @team.save
15
+ # Use to_json for now; we can filter it later.
16
+ [201, @team.to_json]
17
+ else
18
+ [422, ErrorFormatter.format(@team.errors.full_messages)]
19
+ end
20
+ end
21
+
22
+ def self.update(request, params)
23
+ return [400, "Must supply a team number!"] unless params[:number]
24
+
25
+ post_data = process_request request
26
+ return post_data if post_data.is_an?(Array)
27
+
28
+ @team = Team.find_by number: params[:number]
29
+
30
+ if @team.nil?
31
+ return [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
32
+ end
33
+
34
+ if @team.update_attributes(post_data)
35
+ @team.to_json
36
+ else
37
+ [422, ErrorFormatter.format(@team.errors.full_messages)]
38
+ end
39
+ end
40
+
41
+ def self.delete(params)
42
+ @team = Team.find_by number: params[:number]
43
+
44
+ if @team
45
+ if @team.destroy
46
+ 204
47
+ else
48
+ [422, ErrorFormatter.format(@team.errors.full_messages)]
49
+ end
50
+ else
51
+ [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
52
+ end
53
+ end
54
+
55
+ def self.show(params)
56
+ @team = Team.find_by number: params[:number]
57
+
58
+ if @team
59
+ @team.to_json
60
+ else
61
+ [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
62
+ end
63
+ end
64
+
65
+ def self.records(params)
66
+ @team = Team.find_by number: params[:number]
67
+
68
+ if @team
69
+ if params[:competition_id]
70
+ @competition = Competition.find params[:competition_id]
71
+
72
+ if @competition
73
+ @team.records.in(match_id: @competition.matches.map { |match| match.id }).to_json
74
+ else
75
+ [404, ErrorFormatter.format(could_not_find(params[:competition_id], "id", "competition"))]
76
+ end
77
+ else
78
+ @team.records.to_json
79
+ end
80
+ else
81
+ [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
82
+ end
83
+ end
84
+
85
+ def self.matches(params)
86
+ @team = Team.find_by number: params[:number]
87
+
88
+ if @team
89
+ # Ensure that the competition ID is valid.
90
+ if params[:competition_id]
91
+ @competition = Competition.find params[:competition_id]
92
+
93
+ return [404, ErrorFormatter.format(could_not_find(params[:competition_id], "id", "competition"))] if @competition.nil?
94
+ end
95
+
96
+ @team.matches(params[:competition_id]).to_json
97
+ else
98
+ [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
99
+ end
100
+ end
101
+
102
+ def self.competitions(params)
103
+ @team = Team.find_by number: params[:number]
104
+
105
+ if @team
106
+ @team.competitions.to_json
107
+ else
108
+ [404, ErrorFormatter.format(could_not_find(params[:number], "number"))]
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,13 @@
1
+ # Libraries that all controllers require.
2
+ require "json"
3
+ require "frecon/error_formatter"
4
+
5
+ require "frecon/controller"
6
+
7
+ require "frecon/controllers/competitions_controller"
8
+ require "frecon/controllers/dump_controller"
9
+ require "frecon/controllers/matches_controller"
10
+ require "frecon/controllers/participations_controller"
11
+ require "frecon/controllers/records_controller"
12
+ require "frecon/controllers/robots_controller"
13
+ require "frecon/controllers/teams_controller"
@@ -0,0 +1,18 @@
1
+ require "logger"
2
+ require "mongoid"
3
+
4
+ require "frecon/models"
5
+
6
+ module FReCon
7
+ class Database
8
+ def self.setup()
9
+ Mongoid.load!(File.join(File.dirname(__FILE__), "mongoid.yml"), ENVIRONMENT)
10
+
11
+ Mongoid.logger.level = Logger::DEBUG
12
+ Mongoid.logger = Logger.new($stdout)
13
+
14
+ Moped.logger.level = Logger::DEBUG
15
+ Moped.logger = Logger.new($stdout)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require "json"
2
+
3
+ module FReCon
4
+ class ErrorFormatter
5
+ def self.format(message)
6
+ case message
7
+ when String
8
+ JSON.generate({ errors: [ message ] })
9
+ when Array
10
+ JSON.generate({ errors: message })
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,194 @@
1
+ require "frecon/base"
2
+
3
+ module FReCon
4
+ class MatchNumber
5
+ POSSIBLE_TYPES = [:practice, :qualification, :quarterfinal, :semifinal, :final]
6
+ ELIMINATION_TYPES = [:quarterfinal, :semifinal, :final]
7
+
8
+ attr_reader :number, :round
9
+
10
+ # MongoDB compatibility methods.
11
+ def self.demongoize(object)
12
+ # `object' should *always* be a string (since MatchNumber#mongoize returns a
13
+ # String which is what is stored in the database)
14
+ raise ArgumentError, "`object' must be a String" unless object.is_a?(String)
15
+
16
+ MatchNumber.new(object)
17
+ end
18
+
19
+ def self.mongoize(object)
20
+ case object
21
+ when MatchNumber
22
+ object.mongoize
23
+ when String, Hash
24
+ MatchNumber.new(object).mongoize
25
+ else
26
+ object
27
+ end
28
+ end
29
+
30
+ def self.evolve(object)
31
+ case object
32
+ when MatchNumber
33
+ object.mongoize
34
+ when String, Hash
35
+ MatchNumber.new(object).mongoize
36
+ else
37
+ object
38
+ end
39
+ end
40
+
41
+ def mongoize
42
+ to_s
43
+ end
44
+
45
+ def initialize(args)
46
+ if args.is_a?(String)
47
+ # Match `args' against the regular expression, described below.
48
+ #
49
+ # This regular expression matches all values where the first group of
50
+ # characters is one of either [ "p", "q", "qf", "sf", "f" ], which is
51
+ # parsed as the "type" of the match. This is followed by an "m" and a
52
+ # group of digits, which is parsed as the "number" of the match.
53
+ #
54
+ # In addition, one can specify a "round number" following the first group
55
+ # of characters such as in eliminations and finals. Often times, there
56
+ # are multiple so-called "rounds" in eliminations, and so the system will
57
+ # optionally capture that round.
58
+ #
59
+ # Also, one can specify a "replay number" following the match number.
60
+ # this is done by appending "r" and a group of digits which is the replay
61
+ # number.
62
+ #
63
+ # Below are listed the match groups and what they are:
64
+ #
65
+ # 1: Match type
66
+ # 2: Round number (optional)
67
+ # 3: Match number
68
+ # 4: Replay string (optional)
69
+ # 5: Replay number (required if #4 is supplied)
70
+ #
71
+ # This behavior may change in the future.
72
+ match_data = args.match(/(p|q|qf|sf|f)([\d]+)?m([\d]+)(r)?([\d]+)?/i)
73
+
74
+ # Whine if we don't have a match (string is incorrectly formatted)
75
+ raise ArgumentError, "string is improperly formatted" unless match_data
76
+
77
+ # Check and set required stuff first, everything else later.
78
+
79
+ # Whine if we don't have a match type
80
+ raise ArgumentError, "match type must be supplied" unless match_data[1]
81
+
82
+ # Parse the match type string
83
+ @type = case match_data[1].downcase
84
+ when "p"
85
+ :practice
86
+ when "q"
87
+ :qualification
88
+ when "qf"
89
+ :quarterfinal
90
+ when "sf"
91
+ :semifinal
92
+ when "f"
93
+ :final
94
+ else
95
+ raise ArgumentError, "match type must be in [\"p\", \"q\", \"qf\", \"sf\", \"f\"]"
96
+ end
97
+
98
+ # Whine if we don't have a match number
99
+ raise ArgumentError, "match number must be supplied" unless match_data[3]
100
+
101
+ # Parse the match number
102
+ @number = match_data[3].to_i
103
+ raise ArgumentError, "match number must be greater than 0" unless @number > 0
104
+
105
+ # Parse the round number, if it is present
106
+ if match_data[2]
107
+ @round = match_data[2].to_i
108
+ raise ArgumentError, "round number must be greater than 0" unless @round > 0
109
+ end
110
+
111
+ # Parse replay match group, store replay number if present.
112
+ @replay_number = match_data[5].to_i if match_data[4] == "r"
113
+ elsif args.is_a?(Hash)
114
+ # type (Symbol or String)
115
+ # number (Integer)
116
+ # round (Integer), optional
117
+ # replay_number (Integer), optional
118
+
119
+ raise TypeError, "type must be a Symbol or String" unless args[:type].is_a?(Symbol) || args[:type].is_a?(String)
120
+ raise ArgumentError, "type must be in #{POSSIBLE_TYPES.inspect}" unless POSSIBLE_TYPES.include?(args[:type].to_sym)
121
+
122
+ @type = args[:type].to_sym
123
+
124
+ raise TypeError, "match number must be an Integer" unless args[:number].is_an?(Integer)
125
+ raise ArgumentError, "match number must be greater than 0" unless args[:number] > 0
126
+
127
+ @number = args[:number]
128
+
129
+ if args[:round]
130
+ raise TypeError, "round number must be an Integer" unless args[:round].is_an?(Integer)
131
+ raise ArgumentError, "round number must be greater than 0" unless args[:round] > 0
132
+
133
+ @round = args[:round]
134
+ end
135
+
136
+ if args[:replay_number]
137
+ raise TypeError, "replay number must be an Integer" unless args[:replay_number].is_an?(Integer)
138
+ raise ArgumentError, "replay number must be greater than 0" unless args[:replay_number] > 0
139
+
140
+ @replay_number = args[:replay_number]
141
+ end
142
+ else
143
+ raise TypeError, "argument must be a String or Hash"
144
+ end
145
+ end
146
+
147
+ def to_s
148
+ type_string = case @type
149
+ when :practice
150
+ "p"
151
+ when :qualification
152
+ "q"
153
+ when :quarterfinal
154
+ "qf"
155
+ when :semifinal
156
+ "sf"
157
+ when :final
158
+ "f"
159
+ end
160
+ match_string = "m#{@number}"
161
+ replay_string = "r#{@replay_number}" if replay?
162
+
163
+ "#{type_string}#{@round}#{match_string}#{replay_string}"
164
+ end
165
+
166
+ def replay?
167
+ !@replay_number.nil? && @replay_number > 0
168
+ end
169
+
170
+ def practice?
171
+ @type == :practice
172
+ end
173
+
174
+ def qualification?
175
+ @type == :qualification
176
+ end
177
+
178
+ def quarterfinal?
179
+ @type == :quarterfinal
180
+ end
181
+
182
+ def semifinal?
183
+ @type == :semifinal
184
+ end
185
+
186
+ def final?
187
+ @type == :final
188
+ end
189
+
190
+ def elimination?
191
+ ELIMINATION_TYPES.include?(@type)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,13 @@
1
+ require "mongoid"
2
+
3
+ module FReCon
4
+ class Model
5
+ def self.inherited(child)
6
+ child.class_eval do
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+ include Mongoid::Attributes::Dynamic
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require "frecon/model"
2
+
3
+ module FReCon
4
+ class Competition < Model
5
+ field :location, type: String
6
+ field :name, type: String
7
+
8
+ has_many :matches, dependent: :destroy
9
+ has_many :participations, dependent: :destroy
10
+
11
+ validates :location, :name, presence: true
12
+ validates :name, uniqueness: true
13
+
14
+ def records
15
+ matches = self.matches
16
+
17
+ Record.in match_id: matches.map(&:id)
18
+ end
19
+
20
+ def teams
21
+ participations = self.participations
22
+
23
+ Team.in id: participations.map(&:team_id)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ require "frecon/model"
2
+ require "frecon/match_number"
3
+
4
+ module FReCon
5
+ class Match < Model
6
+ field :number, type: MatchNumber
7
+
8
+ field :blue_score, type: Integer, default: 0
9
+ field :red_score, type: Integer, default: 0
10
+
11
+ belongs_to :competition
12
+ has_many :records, dependent: :destroy
13
+
14
+ validates :number, :competition_id, presence: true
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ require "frecon/model"
2
+
3
+ module FReCon
4
+ class Participation < Model
5
+ belongs_to :competition
6
+ belongs_to :team
7
+
8
+ validates :competition_id, :team_id, presence: true
9
+ validates :team_id, uniqueness: { scope: :competition_id }
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require "frecon/model"
2
+ require "frecon/position"
3
+
4
+ module FReCon
5
+ class Record < Model
6
+ field :notes, type: String
7
+ field :position, type: Position
8
+
9
+ belongs_to :match
10
+ belongs_to :team
11
+
12
+ validates :position, :match_id, :team_id, presence: true
13
+
14
+ def competition
15
+ self.match.competition
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require "frecon/model"
2
+
3
+ module FReCon
4
+ class Robot < Model
5
+ field :name, type: String
6
+
7
+ belongs_to :competition
8
+ belongs_to :team
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ require "frecon/model"
2
+
3
+ module FReCon
4
+ class Team < Model
5
+ field :number, type: Integer
6
+
7
+ field :location, type: String
8
+ field :logo_path, type: String
9
+ field :name, type: String
10
+
11
+ has_many :participations, dependent: :destroy
12
+ has_many :records, dependent: :destroy
13
+
14
+ validates :number, presence: true, uniqueness: true, numericality: { greater_than: 0 }
15
+
16
+ def self.number(team_number)
17
+ # Team.find_by number: team_number
18
+ find_by number: team_number
19
+ end
20
+
21
+ def competitions
22
+ Competition.in id: participations.map(&:competition_id)
23
+ end
24
+
25
+ # Returns all of the matches that this team has been in.
26
+ # Optionally, returns the matches that this team has played
27
+ # in a certain competition.
28
+ def matches(competition_id = nil)
29
+ matches = Match.in record_id: self.records.map(&:id)
30
+ matches = matches.where competition_id: competition_id unless competition_id.nil?
31
+
32
+ matches
33
+ end
34
+
35
+ # alias_method works by default solely on instance
36
+ # methods, so change context to the metaclass of
37
+ # Team and do aliasing there.
38
+ class << self
39
+ alias_method :with_number, :number
40
+ alias_method :that_has_number, :number
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ require "frecon/models/competition"
2
+ require "frecon/models/match"
3
+ require "frecon/models/participation"
4
+ require "frecon/models/record"
5
+ require "frecon/models/robot"
6
+ require "frecon/models/team"
@@ -0,0 +1,19 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: frecon
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ use_utc: true
9
+ raise_not_found_error: false
10
+
11
+ production:
12
+ sessions:
13
+ default:
14
+ database: frecon
15
+ hosts:
16
+ - localhost:27017
17
+ options:
18
+ use_utc: true
19
+ raise_not_found_error: false
@@ -0,0 +1,117 @@
1
+ require "frecon/base"
2
+
3
+ module FReCon
4
+ class Position
5
+ attr_reader :alliance, :number
6
+
7
+ # MongoDB compatibility methods.
8
+ def self.demongoize(object)
9
+ # `object' should *always* be a string (since MatchNumber#mongoize returns a
10
+ # String which is what is stored in the database)
11
+ raise ArgumentError, "`object' must be a String" unless object.is_a?(String)
12
+
13
+ Position.new(object)
14
+ end
15
+
16
+ # Allows passing a String or Hash instead of a Position.
17
+ # i.e. record.position = "r3"
18
+ def self.mongoize(object)
19
+ case object
20
+ when Position
21
+ object.mongoize
22
+ when String
23
+ Position.new(object).mongoize
24
+ when Hash
25
+ Position.new(object[:alliance], object[:number]).mongoize
26
+ else
27
+ object
28
+ end
29
+ end
30
+
31
+ # Used for queries.
32
+ def self.evolve(object)
33
+ case object
34
+ when Position
35
+ object.mongoize
36
+ when String
37
+ Position.new(object).mongoize
38
+ when Hash
39
+ Position.new(object[:alliance], object[:number]).mongoize
40
+ else
41
+ object
42
+ end
43
+ end
44
+
45
+ def mongoize
46
+ to_s
47
+ end
48
+
49
+ def initialize(*args)
50
+ if args.length == 1
51
+ # Match `string' against the regular expression, described below.
52
+ #
53
+ # This regular expression matches all values for `string' where
54
+ # the first letter is either "r" or "b" (case-insensitive due to /i
55
+ # at the end of the regular expression) and the last one-or-more
56
+ # characters in the string are digits 0-9. Anything between those two
57
+ # that is either a letter or an underscore is not retained, but
58
+ # if other characters exist (e.g. spaces as of right now) `string'
59
+ # will not match.
60
+ #
61
+ # You can use any words you like if you have more than just
62
+ # "r<n>" or "b<n>", for example "red_2" matches just the same
63
+ # as "r2", or, just for fun, just the same as "royal______2".
64
+ #
65
+ # This behavior may change in the future.
66
+ match_data = args[0].match(/^([rb])[a-z\_]*([0-9]+)/i)
67
+
68
+ # Note: if matched at all, match_data[0] is the entire
69
+ # string that was matched, hence the indices that start
70
+ # at one.
71
+
72
+ raise ArgumentError, "string is improperly formatted" unless match_data
73
+
74
+ @alliance = case match_data[1].downcase
75
+ when "b"
76
+ :blue
77
+ when "r"
78
+ :red
79
+ else
80
+ raise ArgumentError, "alliance character must be in [\"b\", \"r\"]"
81
+ end
82
+
83
+ position_number = match_data[2].to_i
84
+ raise ArgumentError, "position number must be in [1, 2, 3]" unless [1, 2, 3].include?(position_number)
85
+
86
+ @number = position_number
87
+ elsif args.length == 2
88
+ raise TypeError, "alliance must be a Symbol or String" unless args[0].is_a?(Symbol) || args[0].is_a?(String)
89
+ raise ArgumentError, "alliance must be in [:blue, :red]" unless [:blue, :red].include?(args[0].to_sym)
90
+
91
+ @alliance = args[0].to_sym
92
+
93
+ raise TypeError, "second argument must be an Integer" unless args[1].is_an?(Integer)
94
+ raise ArgumentError, "second argument must be in [1, 2, 3]" unless [1, 2, 3].include?(args[1])
95
+
96
+ @number = args[1]
97
+ else
98
+ raise ArgumentError, "wrong number of arguments (#{args.length} for [1, 2])"
99
+ end
100
+ end
101
+
102
+ def to_s
103
+ "#{@alliance[0]}#{@number}"
104
+ end
105
+
106
+ def is_blue?
107
+ @alliance == :blue
108
+ end
109
+
110
+ def is_red?
111
+ @alliance == :red
112
+ end
113
+
114
+ alias_method :was_blue?, :is_blue?
115
+ alias_method :was_red?, :is_red?
116
+ end
117
+ end
@@ -0,0 +1,119 @@
1
+ require "frecon/controllers"
2
+
3
+ module FReCon
4
+ module Routes
5
+ def self.resource_routes(base, name, controller, methods = [:create, :update, :delete, :show, :index])
6
+ if methods.include?(:create)
7
+ base.post "/#{name}" do
8
+ controller.create request, params
9
+ end
10
+ end
11
+
12
+ if methods.include?(:update)
13
+ base.put "/#{name}/:id" do
14
+ controller.update request, params
15
+ end
16
+ end
17
+
18
+ if methods.include?(:delete)
19
+ base.delete "/#{name}/:id" do
20
+ controller.delete params
21
+ end
22
+ end
23
+
24
+ if methods.include?(:show)
25
+ base.get "/#{name}/:id" do
26
+ controller.show params
27
+ end
28
+ end
29
+
30
+ if methods.include?(:index)
31
+ base.get "/#{name}" do
32
+ controller.index params
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.included(base)
38
+ resource_routes base, "teams", TeamsController, [:create, :index]
39
+
40
+ base.put "/teams/:number" do
41
+ TeamsController.update request, params
42
+ end
43
+
44
+ base.delete "/teams/:number" do
45
+ TeamsController.delete params
46
+ end
47
+
48
+ base.get "/teams/:number" do
49
+ TeamsController.show params
50
+ end
51
+
52
+ base.get "/teams/:number/records/?:competition_id?" do
53
+ TeamsController.records params
54
+ end
55
+
56
+ base.get "/teams/:number/matches/?:competition_id?" do
57
+ TeamsController.matches params
58
+ end
59
+
60
+ base.get "/teams/:number/competitions" do
61
+ TeamsController.competitions params
62
+ end
63
+
64
+ resource_routes base, "competitions", CompetitionsController
65
+
66
+ base.get "/competitions/:id/teams" do
67
+ CompetitionsController.teams params
68
+ end
69
+
70
+ base.get "/competitions/:id/matches" do
71
+ CompetitionsController.matches params
72
+ end
73
+
74
+ base.get "/competitions/:id/records" do
75
+ CompetitionsController.records params
76
+ end
77
+
78
+ resource_routes base, "matches", MatchesController
79
+
80
+ base.get "/matches/:id/records" do
81
+ MatchesController.records params
82
+ end
83
+
84
+ base.get "/matches/:id/competition" do
85
+ MatchesController.competition params
86
+ end
87
+
88
+ resource_routes base, "records", RecordsController
89
+
90
+ base.get "/records/:id/competition" do
91
+ RecordsController.competition params
92
+ end
93
+
94
+ resource_routes base, "robots", RobotsController
95
+
96
+ base.get "/robots/:id/competition" do
97
+ RobotsController.competition params
98
+ end
99
+
100
+ base.get "/robots/:id/team" do
101
+ RobotsController.team params
102
+ end
103
+
104
+ resource_routes base, "participations", ParticipationsController
105
+
106
+ base.get "/participations/:id/competition" do
107
+ ParticipationsController.competition params
108
+ end
109
+
110
+ base.get "/participations/:id/team" do
111
+ ParticipationsController.team params
112
+ end
113
+
114
+ base.get "/dump" do
115
+ DumpController.full params
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,23 @@
1
+ require "sinatra/base"
2
+
3
+ require "frecon/database"
4
+ require "frecon/routes"
5
+ require "frecon/controllers"
6
+
7
+ module FReCon
8
+ class Server < Sinatra::Base
9
+ include Routes
10
+
11
+ configure do
12
+ Database.setup
13
+ end
14
+
15
+ before do
16
+ content_type "application/json"
17
+ end
18
+
19
+ def self.start
20
+ run!
21
+ end
22
+ end
23
+ end
data/lib/frecon.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "frecon/base"
2
+
3
+ require "frecon/database"
4
+ require "frecon/server"
5
+ require "frecon/console"
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frecon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sam Craig
8
+ - Kristofer Rye
9
+ - Christopher Cooper
10
+ - Sam Mercier
11
+ - Tiger Huang
12
+ - Vincent Mai
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2015-09-01 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: sinatra
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - "~>"
23
+ - !ruby/object:Gem::Version
24
+ version: '1.4'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - "~>"
30
+ - !ruby/object:Gem::Version
31
+ version: '1.4'
32
+ - !ruby/object:Gem::Dependency
33
+ name: thin
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.6'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.6'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mongoid
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '4.0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '4.0'
60
+ description: A JSON API for scouting FRC competitions that manages the database for
61
+ the user.
62
+ email: sammidysam@gmail.com
63
+ executables:
64
+ - frecon
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - bin/frecon
69
+ - lib/frecon.rb
70
+ - lib/frecon/base.rb
71
+ - lib/frecon/base/object.rb
72
+ - lib/frecon/base/variables.rb
73
+ - lib/frecon/console.rb
74
+ - lib/frecon/controller.rb
75
+ - lib/frecon/controllers.rb
76
+ - lib/frecon/controllers/competitions_controller.rb
77
+ - lib/frecon/controllers/dump_controller.rb
78
+ - lib/frecon/controllers/matches_controller.rb
79
+ - lib/frecon/controllers/participations_controller.rb
80
+ - lib/frecon/controllers/records_controller.rb
81
+ - lib/frecon/controllers/robots_controller.rb
82
+ - lib/frecon/controllers/teams_controller.rb
83
+ - lib/frecon/database.rb
84
+ - lib/frecon/error_formatter.rb
85
+ - lib/frecon/match_number.rb
86
+ - lib/frecon/model.rb
87
+ - lib/frecon/models.rb
88
+ - lib/frecon/models/competition.rb
89
+ - lib/frecon/models/match.rb
90
+ - lib/frecon/models/participation.rb
91
+ - lib/frecon/models/record.rb
92
+ - lib/frecon/models/robot.rb
93
+ - lib/frecon/models/team.rb
94
+ - lib/frecon/mongoid.yml
95
+ - lib/frecon/position.rb
96
+ - lib/frecon/routes.rb
97
+ - lib/frecon/server.rb
98
+ homepage: https://github.com/scouting-project/scouting-project
99
+ licenses: []
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.4.5.1
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: A JSON API for scouting FRC competitions.
121
+ test_files: []
122
+ has_rdoc: