diff --git a/calendar/Readme.md b/calendar/Readme.md
new file mode 100644
index 0000000..e69de29
diff --git a/calendar/escalendar.ecf b/calendar/escalendar.ecf
new file mode 100644
index 0000000..a8b847d
--- /dev/null
+++ b/calendar/escalendar.ecf
@@ -0,0 +1,30 @@
+
+
+
+
+
+ /CVS$
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar/src/calendar_date.e b/calendar/src/calendar_date.e
new file mode 100644
index 0000000..a19d889
--- /dev/null
+++ b/calendar/src/calendar_date.e
@@ -0,0 +1,35 @@
+note
+ description: "Summary description for {CALENDAR_DATE}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CALENDAR_DATE
+
+ create
+ make
+
+ feature
+
+ make(d:DATE; dt: DATE_TIME; tz: STRING)
+local
+ tools :DATE_TIME_TOOLS
+ do
+ a_date := d
+
+
+
+
+
+ a_date_time := dt
+ a_time_zone := tz
+
+ end
+
+ a_date:DATE
+ a_date_time : DATE_TIME
+ a_time_zone : STRING
+
+
+end
diff --git a/calendar/src/calendar_date_payload.e b/calendar/src/calendar_date_payload.e
new file mode 100644
index 0000000..c9c9f98
--- /dev/null
+++ b/calendar/src/calendar_date_payload.e
@@ -0,0 +1,102 @@
+note
+ description: "Summary description for {CALENDAR_START_PAYLOAD}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CALENDAR_DATE_PAYLOAD
+
+ inherit
+ JSON_SERIALIZABLE
+ undefine
+ default_create
+ redefine
+ json_out,
+ eiffel_date_to_json_string,
+ eiffel_date_time_to_json_string
+ end
+
+create
+ make_with_date,
+ default_create
+
+
+feature
+
+make_with_date(d: CALENDAR_DATE)
+do
+ date := d.a_date
+ datetime := d.a_date_time
+ timezone := d.a_time_zone
+ end
+
+
+default_create
+do
+ create date.make_now
+ create datetime.make_now
+ create timezone.make_empty
+ end
+
+ date: DATE
+ -- What is the `date' of the Event?
+ attribute
+ create Result.make_now
+ end
+
+ datetime: DATE_TIME
+ -- What is the `datetime' of the Event?
+ attribute
+ create Result.make_now
+ end
+
+ timeZone: STRING
+ -- What `timezone' is the Event in?
+ attribute
+ create Result.make_empty
+ end
+feature -- Implementation
+
+ eiffel_date_to_json_string (a_key: STRING; a_date: DATE): JSON_STRING
+ -- Convert `a_date' to JSON_STRING with `a_key'
+ do
+ create Result.make_from_string_32 (a_date.formatted_out ("YYYY-[0]MM-[0]DD"))
+ end
+
+
+ eiffel_date_time_to_json_string (a_key: STRING; a_date_time: DATE_TIME): JSON_STRING
+ -- Convert `a_date_time' to JSON_STRING with `a_key'
+ do
+ create Result.make_from_string_32 (a_date_time.formatted_out ("YYYY-[0]MM-[0]DD") + "T" + a_date_time.formatted_out ("[0]hh:[0]mi")+ ":00")
+ end
+
+
+
+ json_out: STRING
+ --
+ -- Convert `end_event' to "end", `datetime' to "dateTime", `timezone' to "timeZone" per Google.
+ do
+ Result := Precursor
+ Result.replace_substring_all ("ending", "end")
+ Result.replace_substring_all ("datetime", "dateTime")
+ Result.replace_substring_all ("timezone", "timeZone")
+ end
+
+ metadata_refreshed (a_current: ANY): ARRAY [JSON_METADATA]
+ do
+ Result := <<
+ create {JSON_METADATA}.make_text_default
+ >>
+ end
+
+ convertible_features (a_object: ANY): ARRAY [STRING]
+ --
+ once
+ Result := <<"datetime","timezone">>
+-- Result := <<"date","dateTime","timeZone">>
+ end
+
+
+
+end
diff --git a/calendar/src/calendar_event.e b/calendar/src/calendar_event.e
new file mode 100644
index 0000000..f990d32
--- /dev/null
+++ b/calendar/src/calendar_event.e
@@ -0,0 +1,42 @@
+note
+ description: "Summary description for {CALENDAR_EVENT}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CALENDAR_EVENT
+
+ create
+ make_generate_id,
+ make
+
+ feature
+
+
+ make(start_date, end_date : CALENDAR_DATE; sum, event_id : STRING)
+ do
+ sd := start_date
+ ed := end_date
+ id := event_id
+ summary := sum
+ end
+
+
+ make_generate_id(start_date, end_date : CALENDAR_DATE)
+ do
+ sd := start_date
+ ed := end_date
+ id := "create unique id"
+ summary := "summary"
+ end
+
+ sd:CALENDAR_DATE
+ ed:CALENDAR_DATE
+ id : STRING
+ summary : STRING
+
+
+
+
+end
diff --git a/calendar/src/calendar_event_payload.e b/calendar/src/calendar_event_payload.e
new file mode 100644
index 0000000..59c734f
--- /dev/null
+++ b/calendar/src/calendar_event_payload.e
@@ -0,0 +1,92 @@
+note
+ description: "Summary description for {CALENDAR_EVENT_PAYLOD}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CALENDAR_EVENT_PAYLOAD
+
+ inherit
+ JSON_SERIALIZABLE
+ undefine
+ default_create
+ redefine
+ json_out
+ end
+
+create
+ make,
+ default_create
+
+
+feature
+
+make (ce: CALENDAR_EVENT )
+do
+ kind:= "calendar#event"
+ summary := ce.summary
+ create start.make_with_date (ce.sd)
+ create ending.make_with_date (ce.ed)
+ id := ce.id
+end
+
+default_create
+do
+ id := ""
+ kind:= ""
+ summary:=""
+ create start
+ create ending
+end
+
+ id : STRING
+ kind: STRING
+ summary: STRING
+ start: CALENDAR_DATE_PAYLOAD
+ ending: CALENDAR_DATE_PAYLOAD
+
+
+
+feature {NONE} -- Implementation: Representation Constants
+
+-- current_representation: STRING
+-- do
+-- Result := "{" +
+-- "%"start%":%"" + start + "%"," +
+-- "%"end%":%"" + ending + "%"" +
+-- "}"
+
+-- end
+
+feature -- Implementation: Mock Features
+
+
+
+
+feature -- Implementation
+ json_out: STRING
+ --
+ -- Convert `end_event' to "end", `datetime' to "dateTime", `timezone' to "timeZone" per Google.
+ do
+ Result := Precursor
+ Result.replace_substring_all ("ending", "end")
+ Result.replace_substring_all ("datetime", "dateTime")
+ Result.replace_substring_all ("timezone", "timeZone")
+ end
+
+
+ metadata_refreshed (a_current: ANY): ARRAY [JSON_METADATA]
+ do
+ Result := <<
+ create {JSON_METADATA}.make_text_default
+ >>
+ end
+
+ convertible_features (a_object: ANY): ARRAY [STRING]
+ --
+ once
+ Result := <<"summary","kind","start", "ending","id">>
+ end
+
+end
diff --git a/calendar/src/eg_calendar_api.e b/calendar/src/eg_calendar_api.e
new file mode 100644
index 0000000..75ffc3f
--- /dev/null
+++ b/calendar/src/eg_calendar_api.e
@@ -0,0 +1,179 @@
+note
+ description: "Summary description for {EG_CALENDAR_API}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ EG_CALENDAR_API
+
+inherit
+ EG_COMMON_API
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_access_token: READABLE_STRING_32)
+ do
+ -- Using a code verifier
+ access_token := a_access_token
+ enable_version_3
+ default_scope
+ end
+
+ default_scope
+ do
+ create {ARRAYED_LIST [STRING_8]} scopes.make (5)
+ add_scope ("https://www.googleapis.com/auth/calendar")
+ end
+
+ enable_version_3
+ -- Enable Google Calendar version v3.
+ do
+ version := "v3"
+ ensure
+ version_set: version.same_string ("v3")
+ end
+
+feature -- Access
+
+ list_calendars: detachable STRING
+ do
+ api_get_call (calendar_url("users/me/calendarList", Void), Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+ list_primary_calendar: detachable STRING
+ do
+ api_get_call (calendar_url("calendars/primary", Void), Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+ list_primary_calendar_events: detachable STRING
+ do
+ api_get_call (calendar_url ("calendars/primary/events", Void), Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+ create_calendar (name_of_calendar: STRING): detachable STRING
+ do
+ api_post_call (calendar_url ("calendars", Void), Void, payload_create_calendar(name_of_calendar), Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+ create_calendar_event (name_of_calendar: STRING; payload: CALENDAR_EVENT_PAYLOAD): detachable STRING
+ require
+ start_date_exists: attached payload.start
+ ending_date_exists: attached payload.ending
+
+ do
+ api_post_call (calendar_url("calendars/" + name_of_calendar + "/events", Void), Void, payload.json_out, Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+ check_event_id (id: STRING) : BOOLEAN
+-- TODO Create a CALENDAR_EVENT_ID to handle the rules.
+
+-- Google doc: Provided IDs must follow these rules:
+-- - characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938
+-- - the length of the ID must be between 5 and 1024 characters
+-- - the ID must be unique per calendar
+
+ do
+ Result := true
+ if id.count <= 5 or id.count >= 1024 then
+ Result := false
+ elseif not id.as_lower.is_equal (id) then
+ Result := false
+ end
+
+ end
+
+
+ update_calendar_event (name_of_calendar: STRING; event_id: STRING; payload: CALENDAR_EVENT_PAYLOAD): detachable STRING
+ require
+ start_date_exists: attached payload.start
+ ending_date_exists: attached payload.ending
+ event_id_follow_google_rules : check_event_id(event_id)
+
+ do
+ api_put_call (calendar_url("calendars/" + name_of_calendar + "/events/" + event_id, Void), Void, payload.json_out, Void)
+ if
+ attached last_response as l_response and then
+ attached l_response.body as l_body
+ then
+ Result := l_body
+ end
+ end
+
+
+
+feature -- Calenader URL
+
+ calendar_url (a_query: STRING; a_params: detachable STRING): STRING
+ -- Calenader url endpoint
+ --| TODO, check if a_params is really neeed.
+ note
+ eis: "name=Calendaer service endpoint", "src=https://developers.google.com/calendar/v3/reference", "protocol=uri"
+ require
+ a_query_attached: a_query /= Void
+ do
+ create Result.make_from_string (endpoint_url)
+ Result.append ("/")
+ Result.append (version)
+ Result.append ("/")
+ Result.append (a_query)
+ if attached a_params then
+ Result.append_character ('?')
+ Result.append (a_params)
+ end
+ ensure
+ Result_attached: Result /= Void
+ end
+
+
+feature -- Access
+
+ endpoint_url: STRING
+ --
+ do
+ Result := "https://www.googleapis.com/calendar"
+ end
+
+ payload_create_calendar(name:STRING): STRING
+ local
+ l_res: JSON_OBJECT
+ do
+ create l_res.make_with_capacity (5)
+ l_res.put_string (name, "summary")
+ Result := l_res.representation
+ end
+
+end
diff --git a/calendar/test/calendar_test_set.e b/calendar/test/calendar_test_set.e
new file mode 100644
index 0000000..1ae679b
--- /dev/null
+++ b/calendar/test/calendar_test_set.e
@@ -0,0 +1,142 @@
+note
+ description: "[
+ Eiffel tests that can be executed by testing tool.
+ ]"
+ author: "EiffelStudio test wizard"
+ date: "$Date$"
+ revision: "$Revision$"
+ testing: "type/manual"
+
+class
+ CALENDAR_TEST_SET
+
+inherit
+ EQA_TEST_SET
+ rename
+ assert as assert_old
+ redefine
+ on_prepare,
+ on_clean
+ end
+
+ EQA_COMMONLY_USED_ASSERTIONS
+ undefine
+ default_create
+ end
+
+
+feature {NONE} -- Events
+
+ on_prepare
+ --
+ do
+-- assert ("not_implemented", False)
+ end
+
+ on_clean
+ --
+ do
+-- assert ("not_implemented", False)
+ end
+
+feature -- Test routines
+
+ test_calendar_event_payload
+ -- New test routine
+ local
+
+ calendar_event_p : CALENDAR_EVENT_PAYLOAD
+ calendar_event : CALENDAR_EVENT
+ start_date : CALENDAR_DATE
+ end_date : CALENDAR_DATE
+
+ d: DATE
+ dt: DATE_TIME
+ tz : STRING
+ cd : CALENDAR_DATE
+ expected_json : STRING
+
+ do
+ create d.make_now
+ create dt.make_now
+ tz := "CET"
+ create start_date.make (d, dt, tz)
+ create end_date.make (d, dt, tz)
+
+
+ create calendar_event.make (start_date, end_date, "test summary", "testcalendareventpaload123")
+ create calendar_event_p.make (calendar_event)
+
+-- Removed date might be temporary expected_json := "{%"start%":{%"date%":%"" + d.formatted_out ("YYYY-[0]MM-[0]DD") + "%",%"dateTime%":%"" + dt.formatted_out ("YYYY-[0]MM-[0]DD") + "T"+ dt.formatted_out ("[0]hh:[0]mi") +
+ expected_json := "{%"start%":{%"dateTime%":%"" + dt.formatted_out ("YYYY-[0]MM-[0]DD") + "T"+ dt.formatted_out ("[0]hh:[0]mi") + ":00" +
+ "%",%"timeZone%":%"" + tz + "%"}" + ",%"end%":" +
+ "{%"dateTime%":%"" + dt.formatted_out ("YYYY-[0]MM-[0]DD") + "T"+ dt.formatted_out ("[0]hh:[0]mi") + ":00" +
+ "%",%"timeZone%":%"" + tz + "%"}" + ",%"kind%":%"calendar#event%",%"summary%":%"test summary%",%"id%":%"testcalendareventpaload123%"}"
+
+
+ assert_strings_equal ("Simple test of attributes", expected_json, calendar_event_p.json_out)
+
+ end
+
+
+ test_calendar_date_payload
+ -- New test routine
+ local
+
+ calendar_date : CALENDAR_DATE_PAYLOAD
+ d: DATE
+ dt: DATE_TIME
+ tz : STRING
+ cd : CALENDAR_DATE
+ expected_json : STRING
+ do
+ create d.make_now
+ create dt.make_now
+ tz := "CET"
+ create cd.make (d, dt, tz)
+
+
+ create calendar_date.make_with_date (cd)
+-- I removed DATE as part of the json. I am not sure that it is necessary but since it is working I will stick to the new implementation
+-- expected_json := "{%"date%":%"" + d.formatted_out ("YYYY-[0]MM-[0]DD") + "%",%"dateTime%":%"" + dt.formatted_out ("YYYY-[0]MM-[0]DD") + "T" + dt.formatted_out ("[0]hh:[0]mi") +
+-- "%",%"timeZone%":%"" + tz + "%"}"
+
+
+ expected_json := "{%"dateTime%":%"" + dt.formatted_out ("YYYY-[0]MM-[0]DD") + "T" + dt.formatted_out ("[0]hh:[0]mi") + ":00" +
+ "%",%"timeZone%":%"" + tz + "%"}"
+
+
+ assert_strings_equal ("Simple test of calendar date", expected_json, calendar_date.json_out)
+
+ end
+
+ test_calendar_event_id
+ local
+ calednar_api : EG_CALENDAR_API
+ do
+ create calednar_api.make ("DUMMAYSTRING")
+
+ assert_booleans_equal ("Should be a correct id", true, calednar_api.check_event_id ("aaaaaaaaaa"))
+ assert_booleans_equal ("Too few characters", false, calednar_api.check_event_id ("aaaa5"))
+ assert_booleans_equal ("Minimum nmber of characters", true, calednar_api.check_event_id ("aaaaa6"))
+ assert_booleans_equal ("Too many characters", false, calednar_api.check_event_id ( create {STRING}.make_filled ('a', 1024)))
+ assert_booleans_equal ("Maximum number if characters", true, calednar_api.check_event_id ( create {STRING}.make_filled ('a', 1023)))
+
+ assert_booleans_equal ("Uppercase not allowed", false, calednar_api.check_event_id ("aaaaAaaaaa"))
+
+ end
+
+
+feature {NONE}
+
+simple_start_end_json : STRING = "[
+{"start":"test","end":"test"}
+]"
+
+simple_start_json : STRING = "[
+{"start":"test"}
+]"
+
+end
+
+
diff --git a/calendar/test/test.ecf b/calendar/test/test.ecf
new file mode 100644
index 0000000..4e9d9da
--- /dev/null
+++ b/calendar/test/test.ecf
@@ -0,0 +1,30 @@
+
+
+
+
+
+ /CVS$
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar/test/test_calendar_api.e b/calendar/test/test_calendar_api.e
new file mode 100644
index 0000000..5c85cb9
--- /dev/null
+++ b/calendar/test/test_calendar_api.e
@@ -0,0 +1,403 @@
+note
+ description: "Summary description for {TEST_SHEETS_WITH_API_KEY}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ TEST_CALENDAR_API
+
+inherit
+
+ APPLICATION_FLOW
+
+ redefine
+ Token_file_path_s,
+ google_auth_path_path_s
+ end
+
+create
+ make
+
+feature -- {NONE}
+
+ make
+ do
+ -- TODO improve this code so we can select which integration test we want to run.
+ logger.write_information ("make-> ======================> Starting application")
+
+ set_from_json_credentials_file_path (create {PATH}.make_from_string (CREDENTIALS_PATH))
+ retrieve_access_token
+
+-- test_list_calendars
+-- test_create_calendar_event
+ test_update_calendar_event
+ -- test_list_primary_calendar
+ -- test_list_primary_calendar_events
+ -- test_list_calendars
+
+ end
+
+feature -- Tests
+
+ Token_file_path_s: STRING
+ do
+ Result := "/home/anders/token.access"
+ end
+
+ google_auth_path_path_s: STRING
+ do
+ Result := "https://www.googleapis.com/auth/calendar"
+ end
+
+ test_list_calendars
+ require
+ token_is_valid
+ local
+ l_esapi: EG_CALENDAR_API
+ do
+ -- https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.list_calendars as l_calendars then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_sheet-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_create_the_spreedsheet: False
+ end
+ else
+ check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Listed Calendars%N")
+ print (l_calendars)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+ test_list_primary_calendar
+ require
+ token_is_valid
+ local
+ l_esapi: EG_CALENDAR_API
+ do
+ -- https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.list_primary_calendar as l_calendars then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_sheet-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_create_the_spreedsheet: False
+ end
+ else
+ check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Listed Calendars%N")
+ print (l_calendars)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+ test_list_primary_calendar_events
+ require
+ token_is_valid
+ local
+ l_esapi: EG_CALENDAR_API
+ do
+ -- https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.list_primary_calendar_events as l_calendars then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_sheet-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_create_the_spreedsheet: False
+ end
+ else
+ check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Listed Calendars%N")
+ print (l_calendars)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+ test_create_calendar
+ require
+ token_is_valid
+ local
+ l_esapi: EG_CALENDAR_API
+ do
+ -- https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.create_calendar ("BSharpABTODO") as l_calendars then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_calendar-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_create_calwnsar: False
+ end
+ else
+ check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Listed Calendars%N")
+ print (l_calendars)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+ test_create_calendar_event
+ require
+ token_is_valid
+ local
+ payload: CALENDAR_EVENT_PAYLOAD
+ l_esapi: EG_CALENDAR_API
+ ce: CALENDAR_EVENT
+ start_date, end_date: CALENDAR_DATE
+ d: DATE
+ dt: DATE_TIME
+ de: DATE
+ dte: DATE_TIME
+
+ do
+ create d.make_now
+ create dt.make_now
+ create start_date.make (d, dt, "Europe/Zurich")
+
+ create de.make_now
+ create dte.make_now
+ dte.minute_add (20)
+
+ create end_date.make (de, dte, "Europe/Zurich")
+ create ce.make (start_date, end_date, "test summary", "createtesteventid123")
+ create payload.make (ce)
+
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.create_calendar_event ("primary", payload) as l_calendar_event then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_calendar event-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_create_the_calednar_event: False
+ end
+ else
+-- check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Created Calednar Event%N")
+ print (l_calendar_event)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+ test_update_calendar_event
+ require
+ token_is_valid
+ local
+ payload: CALENDAR_EVENT_PAYLOAD
+ l_esapi: EG_CALENDAR_API
+ ce: CALENDAR_EVENT
+ start_date, end_date: CALENDAR_DATE
+ d: DATE
+ dt: DATE_TIME
+ de: DATE
+ dte: DATE_TIME
+
+ do
+ create d.make_now
+ create dt.make_now
+ create start_date.make (d, dt, "Europe/Zurich")
+
+ create de.make_now
+ create dte.make_now
+ dte.minute_add (60)
+
+ create end_date.make (de, dte, "Europe/Zurich")
+ create ce.make (start_date, end_date, "test summary", "eventidupdatetest123")
+ create payload.make (ce)
+
+ create l_esapi.make (last_token.token)
+ if attached l_esapi.update_calendar_event ("primary","testid", payload) as l_calendar_event then
+ if l_esapi.has_error then
+ -- debug ("test_create_sheet")
+ logger.write_error ("test_create_calendar event-> Error")
+ print ("test_create_sheet-> Error: msg:" + l_esapi.error_message + "%N")
+ print ("test_create_sheet-> See codes here: https://developers.google.com/maps-booking/reference/rest-api-v3/status_codes")
+ print ("%N")
+ -- end
+ check
+ cannot_update_the_calednar_event: False
+ end
+ else
+-- check Json_Field_spreadsheetId: l_calendars.has_substring ("calendar") end
+ -- check Json_Field_spreadsheetId: l_spreedsheet.has_substring ("spreadsheetId calendarListEntry") end
+ -- check Json_Field_properties: l_spreedsheet.has_substring ("properties") end
+ -- check Json_Field_sheets: l_spreedsheet.has_substring ("sheets") end
+ -- check Json_Field_spreadsheetUrl: l_spreedsheet.has_substring ("spreadsheetUrl") end
+ -- developerMetadata and namedRanges are optional.
+ -- debug ("test_create_sheet")
+ print ("Created Calednar Event%N")
+ print (l_calendar_event)
+ print ("%N")
+ -- end
+ end
+ else
+ -- Bad scope. no connection, etc
+ check Unexptected_Behavior: False end
+ end
+ end
+
+
+
+
+feature {NONE} -- Implementations
+
+ CREDENTIALS_PATH: STRING = "/home/anders/credentials.json" -- get this file from https://console.developers.google.com/
+-- CREDENTIALS_PATH: STRING = "credentials.json" -- get this file from https://console.developers.google.com/
+ -- Credentials path to json file.
+
+-- impl_append_post_data_sample: STRING
+-- local
+-- l_res: JSON_OBJECT
+-- l_jsa_main,
+-- l_jsa_line: JSON_ARRAY
+-- j_array: JSON_ARRAY
+
+-- --{
+-- -- "range": string,
+-- -- "majorDimension": enum (Dimension),
+-- -- "values": [
+-- -- array
+-- -- ]
+-- --}
+-- --// "values": [
+-- -- // [
+-- -- // "Item",
+-- -- // "Cost"
+-- -- // ],
+-- -- // [
+-- -- // "Wheel",
+-- -- // "$20.50"
+-- -- // ],
+-- -- // [
+-- -- // "Door",
+-- -- // "$15"
+-- -- // ],
+-- -- // [
+-- -- // "Engine",
+-- -- // "$100"
+-- -- // ],
+-- -- // [
+-- -- // "Totals",
+-- -- // "$135.50"
+-- -- // ]
+-- -- // ]
+
+-- do
+-- create l_res.make_with_capacity (5)
+-- l_res.put_string ("Sheet1!A1:B5", "range")
+-- l_res.put_string ("ROWS", "majorDimension") -- "DIMENSION_UNSPECIFIED", "ROWS", "COLUMNS"
+
+-- create l_jsa_main.make (10)
+
+-- create j_array.make (1)
+-- create l_jsa_line.make (2)
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Item"))
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Cost"))
+-- j_array.add (l_jsa_line)
+
+-- create l_jsa_line.make (2)
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Wheel"))
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("$20.50"))
+-- j_array.add (l_jsa_line)
+
+-- create l_jsa_line.make (2)
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Door"))
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("$15"))
+-- j_array.add (l_jsa_line)
+
+-- create l_jsa_line.make (2)
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Engine"))
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("$100"))
+-- j_array.add (l_jsa_line)
+
+-- create l_jsa_line.make (2)
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("Totals"))
+-- l_jsa_line.extend (create {JSON_STRING}.make_from_string ("$135.50"))
+-- j_array.add (l_jsa_line)
+
+-- l_res.put (j_array, "values")
+
+-- Result := l_res.representation
+-- logger.write_debug ("impl_append_body-> Result: '" + Result.out + "'")
+-- end
+
+end
diff --git a/gsuite_base/Readme.md b/gsuite_base/Readme.md
new file mode 100644
index 0000000..e69de29
diff --git a/sheets/test/application_flow.e b/gsuite_base/application_flow.e
similarity index 61%
rename from sheets/test/application_flow.e
rename to gsuite_base/application_flow.e
index 0d879fd..c5dfc2b 100644
--- a/sheets/test/application_flow.e
+++ b/gsuite_base/application_flow.e
@@ -19,10 +19,12 @@ feature {NONE} -- Initialization
local
l_secs: INTEGER
do
+ retrieve_access_token_error_has_occured := false
create last_token.make_empty
get_token
if last_token.token.is_empty then
logger.write_warning ("retrieve_access_token-> There is something wrong token is empty from file_path: " + Token_file_path_s)
+ retrieve_access_token_error_has_occured := true
check
not_happening: False
end
@@ -31,13 +33,29 @@ feature {NONE} -- Initialization
end
end
+ read_token_from_file
+ local
+ file: FILE
+ token: OAUTH_TOKEN
+ do
+ create {PLAIN_TEXT_FILE} file.make_with_name (Token_file_path_s)
+ if file.exists then
+ file.open_read
+ file.read_stream (file.count)
+ if attached {OAUTH_TOKEN} deserialize (file.last_string) as l_token then
+ last_token := l_token
+ end
+ end
+ end
+
+
get_token
local
file: FILE
token: OAUTH_TOKEN
l_date_file: DATE_TIME
l_date_now: DATE_TIME
- l_diff: INTEGER_64
+ l_diff: INTEGER_64
do
create {PLAIN_TEXT_FILE} file.make_with_name (Token_file_path_s)
if file.exists then
@@ -67,6 +85,78 @@ feature {NONE} -- Initialization
last_token := token
end
+ get_authorization_url: STRING
+ require
+ attached api_key
+ attached api_secret
+ local
+ google: OAUTH_20_GOOGLE_API
+ config: OAUTH_CONFIG
+ file: FILE
+ do
+ check
+ attached api_key as l_api_key
+ attached api_secret as l_api_secret
+ then
+ logger.write_debug ("get_token_from_url-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'")
+ create Result.make_empty
+ create config.make_default (l_api_key, l_api_secret)
+ config.set_callback ("urn:ietf:wg:oauth:2.0:oob")
+ config.set_scope (google_auth_path_path_s)
+ create google
+ my_api_service := google.create_service (config)
+ logger.write_debug ("%N===Google OAuth Workflow ===%N")
+
+ -- Obtain the Authorization URL
+ logger.write_debug ("%NFetching the Authorization URL...");
+
+ if attached my_api_service as api_service then
+ if attached api_service.authorization_url (empty_token) as lauthorization_url then
+ logger.write_debug ("%NGot the Authorization URL!%N");
+ logger.write_debug ("%NNow go and authorize here:%N");
+ Result := lauthorization_url
+ end
+ end
+ end
+ end
+
+ my_api_service: detachable OAUTH_SERVICE_I
+
+
+ set_token( autorization_code : STRING)
+ require
+ attached api_key
+ attached api_secret
+ attached my_api_service
+ local
+ google: OAUTH_20_GOOGLE_API
+ config: OAUTH_CONFIG
+-- api_service: OAUTH_SERVICE_I
+ file: FILE
+ do
+ check
+ attached api_key as l_api_key
+ attached api_secret as l_api_secret
+ then
+-- logger.write_debug ("get_token_from_url-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'")
+
+-- create config.make_default (l_api_key, l_api_secret)
+-- config.set_callback ("urn:ietf:wg:oauth:2.0:oob")
+-- config.set_scope (google_auth_path_path_s)
+-- create google
+-- api_service := google.create_service (config)
+-- logger.write_debug ("%N===Google OAuth Workflow ===%N")
+ if attached my_api_service as api_service then
+ if attached api_service.access_token_post (empty_token, create {OAUTH_VERIFIER}.make (autorization_code)) as access_token then
+ create {PLAIN_TEXT_FILE} file.make_create_read_write (Token_file_path_s)
+ file.put_string (serialize (access_token))
+ file.flush
+ file.close
+ end
+ end
+ end
+ end
+
get_token_from_url: OAUTH_TOKEN
require
attached api_key
@@ -81,11 +171,11 @@ feature {NONE} -- Initialization
attached api_key as l_api_key
attached api_secret as l_api_secret
then
- logger.write_debug ("get_token_from_url-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'" )
+ logger.write_debug ("get_token_from_url-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'")
create Result.make_empty
create config.make_default (l_api_key, l_api_secret)
config.set_callback ("urn:ietf:wg:oauth:2.0:oob")
- config.set_scope ("https://www.googleapis.com/auth/spreadsheets")
+ config.set_scope (google_auth_path_path_s)
create google
api_service := google.create_service (config)
logger.write_debug ("%N===Google OAuth Workflow ===%N")
@@ -112,10 +202,10 @@ feature {NONE} -- Initialization
refresh_access_token (a_token: OAUTH_TOKEN): OAUTH_TOKEN
-- https://developers.google.com/identity/protocols/oauth2/limited-input-device#offline
- --client_id=your_client_id&
- --client_secret=your_client_secret&
- --refresh_token=refresh_token&
- --grant_type=refresh_token
+ --client_id=your_client_id&
+ --client_secret=your_client_secret&
+ --refresh_token=refresh_token&
+ --grant_type=refresh_token
require
attached api_key
attached api_secret
@@ -130,17 +220,26 @@ feature {NONE} -- Initialization
attached api_key as l_api_key
attached api_secret as l_api_secret
then
- logger.write_debug ("refresh_access_token-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'" )
+ logger.write_debug ("refresh_access_token-> api_key:'" + l_api_key + "' secret:'" + l_api_secret + "'")
create Result.make_empty
create google
- create request.make ("POST", google.access_token_endpoint )
+ create request.make ("POST", google.access_token_endpoint)
request.add_body_parameter ("client_id", l_api_key)
request.add_body_parameter ("client_secret", l_api_secret)
request.add_body_parameter ("refresh_token", if attached a_token.refresh_token as l_token then l_token else "" end)
+ if attached a_token.refresh_token as l_token then
+ logger.write_debug ("refresh_access_token-> refresh_token: " + l_token)
+ end
request.add_body_parameter ("grant_type", "refresh_token")
if attached request.execute as l_response then
+ logger.write_debug ("refresh_access_token-> Got Response")
if attached l_response.body as l_body then
+ logger.write_debug ("refresh_access_token-> Response included body" + l_body)
if attached {OAUTH_TOKEN} google.access_token_extractor.extract (l_body) as l_access_token then
+ logger.write_debug ("refresh_access_token-> Updating token")
+ if attached a_token.refresh_token as l_token then
+ l_access_token.set_refresh_token (l_token)
+ end
create {PLAIN_TEXT_FILE} file.make_create_read_write (Token_file_path_s)
file.put_string (serialize (l_access_token))
Result := l_access_token
@@ -154,11 +253,19 @@ feature {NONE} -- Initialization
feature -- Access
- Token_file_path_s: STRING = "token.access"
+ Token_file_path_s: STRING
+ do
+ Result := "token.access"
+ end
+
+ google_auth_path_path_s: STRING
+ do
+ Result := "https://www.googleapis.com/auth/spreadsheets"
+ end
feature -- Status Setting
- set_from_json_credentials_file_path (an_fp: PATH)
+ set_from_json_credentials_file_path (an_fp: PATH) --TODO if file does not exists it shuold not fail. It should try to create a file with correct information.
-- sets api_key and api_secret from given api credentials file path normally provided by google -> https://console.developers.google.com
-- create a Create OAuth client ID -> desktop app -> and export it to a json file with download link
local
@@ -177,9 +284,9 @@ feature -- Status Setting
check
valid_main_object: attached {JSON_OBJECT} l_json_parser.parsed_json_value as l_main_json_o
then
+ -- Installed
check
- valid_installed: attached {JSON_OBJECT} l_main_json_o.item ("installed") as l_installed_jso
- then
+ valid_installed: attached {JSON_OBJECT} l_main_json_o.item ("installed") as l_installed_jso then
check
has_client_id: attached {JSON_STRING} l_installed_jso.item ("client_id") as l_client_id_js_s
then
@@ -205,6 +312,28 @@ feature -- Status report
Result := not last_token.token.is_empty
end
+ has_expired: BOOLEAN
+ require
+ valid_token: token_is_valid
+ local
+ file: FILE
+ l_date_file: DATE_TIME
+ l_date_now: DATE_TIME
+ l_diff: INTEGER_64
+ do
+ read_token_from_file
+ create {PLAIN_TEXT_FILE} file.make_with_name (Token_file_path_s)
+ create l_date_file.make_from_epoch (file.date)
+ create l_date_now.make_now_utc
+ l_diff := l_date_now.definite_duration (l_date_file).seconds_count
+ if l_diff > last_token.expires_in-300 then
+ Result := True
+ end
+ end
+
+ retrieve_access_token_error_has_occured : BOOLEAN
+
+
feature -- Serialize Access Token
serialize (a_object: ANY): STRING
@@ -260,4 +389,5 @@ feature {NONE} -- Implementation
api_key: detachable STRING
api_secret: detachable STRING
empty_token: detachable OAUTH_TOKEN
+
end
diff --git a/gsuite_base/eg_base.ecf b/gsuite_base/eg_base.ecf
new file mode 100644
index 0000000..1adaa04
--- /dev/null
+++ b/gsuite_base/eg_base.ecf
@@ -0,0 +1,31 @@
+
+
+
+
+
+ /CVS$
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gsuite_base/eg_common_api.e b/gsuite_base/eg_common_api.e
new file mode 100644
index 0000000..a686302
--- /dev/null
+++ b/gsuite_base/eg_common_api.e
@@ -0,0 +1,370 @@
+note
+ description: "Common abstraction to access (read/write Google API data)"
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ EG_COMMON_API
+
+inherit
+
+ LOGGABLE
+
+feature -- Reset
+
+ reset
+ do
+ create access_token.make_empty
+ end
+
+feature -- Access
+
+ access_token: STRING_32
+ -- Google OAuth2 access token.
+
+ http_status: INTEGER
+ -- Contains the last HTTP status code returned.
+
+ last_api_call: detachable STRING
+ -- Contains the last API call.
+
+ last_response: detachable OAUTH_RESPONSE
+ -- Cointains the last API response.
+
+ version: STRING_8
+ -- Google API version.
+
+ scopes: LIST [STRING_8]
+ --Lists of Authorization Scopes.
+
+feature -- Scopes
+
+ add_scope (a_scope: STRING_8)
+ -- Add an scope `a_scope` to the list of scopes.
+ do
+ scopes.force (a_scope)
+ end
+
+ clear_scopes
+ -- Remove all items.
+ do
+ scopes.wipe_out
+ ensure
+ scopes.is_empty
+ end
+
+feature -- Query Parameters Factory
+
+ query_parameters (a_params: detachable STRING_TABLE [STRING] ): detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]]
+ -- @JV please add a call example
+ local
+ l_result: like query_parameters
+ l_tuple : like query_parameters.item
+ i: INTEGER
+ do
+ if attached a_params then
+ i := 1
+ create l_result.make_filled (Void, 1, a_params.count)
+ across a_params as ic
+ loop
+ create l_tuple.default_create
+ l_tuple.put (ic.key, 1)
+ l_tuple.put (ic.item, 2)
+ l_result.force (l_tuple, i)
+ i := i + 1
+ end
+ Result := l_result
+ end
+ end
+
+feature -- Error Report
+
+ parse_last_response
+ require
+ attached last_response
+ local
+ l_json_parser: JSON_PARSER
+ do
+ check
+ attached last_response as l_response
+ then
+ if l_response.status = {HTTP_STATUS_CODE}.unauthorized then
+ logger.write_error ("parse_last_response->Unauthorized status, review your authorization credentials")
+ end
+ if attached l_response.body as l_body then
+ logger.write_debug ("parse_last_response->body:" + l_body)
+ create l_json_parser.make_with_string (l_body)
+ l_json_parser.parse_content
+ if l_json_parser.is_valid then
+ if attached {JSON_OBJECT} l_json_parser.parsed_json_value as l_main_jso then
+ if attached {JSON_OBJECT} l_main_jso.item ("error") as l_error_jso then
+ if attached {JSON_NUMBER} l_error_jso.item ("code") as l_jso then
+ print ("parse_last_response-> error code:" + l_jso.representation + "%N")
+ end
+ if attached {JSON_STRING} l_error_jso.item ("message") as l_jso then
+ print ("parse_last_response-> error message:" + l_jso.unescaped_string_8 + "%N")
+ end
+ if attached {JSON_STRING} l_error_jso.item ("status") as l_jso then
+ print ("parse_last_response-> error status:" + l_jso.unescaped_string_8 + "%N")
+ end
+ end
+ end
+ else
+ print ("parse_last_response-> Error: Invalid json body content:" + l_body + "%N")
+ end
+ end
+ end
+ end
+
+ has_error: BOOLEAN
+ -- Last api call raise an error?
+ do
+ if attached last_response as l_response then
+ Result := l_response.status /= 200
+ else
+ Result := False
+ end
+ end
+
+ error_message: STRING
+ -- Last api call error message.
+ require
+ has_error: has_error
+ do
+ if
+ attached last_response as l_response
+ then
+ if
+ attached l_response.error_message as l_error_message
+ then
+ Result := l_error_message
+ else
+ Result := l_response.status.out
+ end
+ else
+ Result := "Unknown Error"
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ api_post_call (a_api_url: STRING; a_query_params: detachable STRING_TABLE [STRING]; a_payload: detachable STRING; a_upload_data: detachable TUPLE[data:PATH; content_type: STRING])
+ note
+ eis: "name=payload_body", "src=https://tools.ietf.org/html/draft-ietf-httpbis-p3-payload-14#section-3.2", "protocol=https://tools.ietf.org/html/draft-ietf-httpbis-p3-payload-14#section-3.2"
+ -- POST REST API call for `a_api_url'
+ do
+ internal_api_call (a_api_url, "POST", a_query_params, a_payload, a_upload_data)
+ end
+
+ api_put_call (a_api_url: STRING; a_query_params: detachable STRING_TABLE [STRING]; a_payload: detachable STRING; a_upload_data: detachable TUPLE[data:PATH; content_type: STRING])
+ note
+ eis: "name=payload_body", "src=https://tools.ietf.org/html/draft-ietf-httpbis-p3-payload-14#section-3.2", "protocol=https://tools.ietf.org/html/draft-ietf-httpbis-p3-payload-14#section-3.2"
+ -- POST REST API call for `a_api_url'
+ do
+ internal_api_call (a_api_url, "PUT", a_query_params, a_payload, a_upload_data)
+ end
+
+
+ api_delete_call (a_api_url: STRING; a_query_params: detachable STRING_TABLE [STRING])
+ -- DELETE REST API call for `a_api_url'
+ do
+ internal_api_call (a_api_url, "DELETE", a_query_params, Void, Void)
+ end
+
+ api_get_call (a_api_url: STRING; a_query_params: detachable STRING_TABLE [STRING])
+ -- GET REST API call for `a_api_url'
+ do
+ internal_api_call (a_api_url, "GET", a_query_params, Void, Void)
+ end
+
+ internal_api_call (a_api_url: STRING; a_method: STRING; a_query_params: detachable STRING_TABLE [STRING]; a_payload: detachable STRING; a_upload_data: detachable TUPLE[filename:PATH; content_type: STRING])
+ note
+ EIS:"name=access token", "src=https://developers.google.com/identity/protocols/oauth2", "protocol=uri"
+ local
+ request: OAUTH_REQUEST
+ l_access_token: detachable OAUTH_TOKEN
+ api_service: OAUTH_20_SERVICE
+ config: OAUTH_CONFIG
+ do
+ logger.write_debug ("internal_api_call-> a_api_url:" + a_api_url + " method:" + a_method)
+ -- TODO improve this, so we can check the required scopes before we
+ -- do an api call.
+ -- TODO add a class with the valid scopes.
+ create config.make_default ("", "")
+
+ -- Add scopes
+ if scopes.is_empty then
+ -- Don't add scopes if the list is empty.
+ else
+ config.set_scope (build_scope)
+ end
+
+ -- Initialization
+
+ create api_service.make (create {OAUTH_20_GOOGLE_API}, config)
+ --| TODO improve cypress service creation procedure to make configuration optional.
+
+ --| TODO rewrite prints as logs
+ logger.write_debug ("%N===Google OAuth Workflow using OAuth access token for the owner of the application ===%N")
+
+ -- Create the access token that will identifies the user making the request.
+ create l_access_token.make_token_secret (access_token, "NOT_NEEDED")
+ --| Todo improve access_token to create a token without a secret.
+ check attached l_access_token as ll_access_token then
+ logger.write_information ("internal_api_call->Got the Access Token:" + ll_access_token.token);
+
+ --Now let's go and check if the request is signed correcty
+ logger.write_information ("internal_api_call->Now we're going to verify our credentials...%N");
+ -- Build the request and authorize it with OAuth.
+ create request.make (a_method, a_api_url)
+
+ -- Workaorund to make it work with Google API
+ -- in other case it return HTTP 411 Length Required.
+ -- https://tools.ietf.org/html/rfc7231#section-6.5.10
+ --
+ --
+ -- https://tools.ietf.org/html/rfc7230#section-3.3.2
+ --
+ upload_data (a_method, request, a_upload_data)
+ add_parameters (a_method, request, a_query_params)
+ -- adding payload
+ if attached a_payload then
+ request.add_header ("Content-length", a_payload.count.out)
+ request.add_header ("Content-Type", "application/json; charset=UTF-8")
+ request.add_payload (a_payload)
+ logger.write_debug ("internal_api_call->payload:'" + a_payload + "'")
+
+ else
+ request.add_header ("Content-length", "0")
+ end
+
+ api_service.sign_request (ll_access_token, request)
+
+ logger.write_debug ("internal_api_call->uri:'" + request.uri + "'")
+ if attached request.upload_file as l_s then
+ logger.write_debug ("internal_api_call->upload file:'" + l_s.out + "'")
+ end
+ if attached {OAUTH_RESPONSE} request.execute as l_response then
+ last_response := l_response
+ end
+ end
+ last_api_call := a_api_url.string
+ end
+
+
+ url (a_base_url: STRING; a_parameters: detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]]): STRING
+ -- url for `a_base_url' and `a_parameters'
+ require
+ a_base_url_attached: a_base_url /= Void
+ do
+ create Result.make_from_string (a_base_url)
+ append_parameters_to_url (Result, a_parameters)
+ end
+
+ append_parameters_to_url (a_url: STRING; a_parameters: detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]])
+ -- Append parameters `a_parameters' to `a_url'
+ require
+ a_url_attached: a_url /= Void
+ local
+ i: INTEGER
+ l_first_param: BOOLEAN
+ do
+ if a_parameters /= Void and then a_parameters.count > 0 then
+ if a_url.index_of ('?', 1) > 0 then
+ l_first_param := False
+ elseif a_url.index_of ('&', 1) > 0 then
+ l_first_param := False
+ else
+ l_first_param := True
+ end
+ from
+ i := a_parameters.lower
+ until
+ i > a_parameters.upper
+ loop
+ if attached a_parameters[i] as a_param then
+ if l_first_param then
+ a_url.append_character ('?')
+ else
+ a_url.append_character ('&')
+ end
+ a_url.append_string (a_param.name)
+ a_url.append_character ('=')
+ a_url.append_string (a_param.value)
+ l_first_param := False
+ end
+ i := i + 1
+ end
+ end
+ end
+
+ add_parameters (a_method: STRING; request: OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
+ -- add parameters 'a_params' (with key, value) to the oauth request 'request'.
+ --| at the moment all params are added to the query_string.
+ do
+ --| TODO check if we need to call add_body_parameters if the method is PUT or POST
+ add_query_parameters (request, a_params)
+ end
+
+ add_query_parameters (request:OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
+ do
+ if attached a_params then
+ across a_params as ic
+ loop
+ request.add_query_string_parameter (ic.key.as_string_8, ic.item)
+ end
+ end
+ end
+
+ add_body_parameters (request:OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
+ do
+ if attached a_params then
+ across a_params as ic
+ loop
+ request.add_body_parameter (ic.key.as_string_8, ic.item)
+ end
+ end
+ end
+
+ upload_data (a_method: STRING; request:OAUTH_REQUEST; a_upload_data: detachable TUPLE[file_name:PATH; content_type: STRING])
+ local
+ l_raw_file: RAW_FILE
+ do
+ if a_method.is_case_insensitive_equal_general ("POST") and then attached a_upload_data as l_upload_data then
+ create l_raw_file.make_open_read (l_upload_data.file_name.absolute_path.name)
+ if l_raw_file.exists then
+ logger.write_debug ("upload_data-> Content-type: '" + l_upload_data.content_type + "'")
+ logger.write_debug ("upload_data-> upload file name: '" + l_upload_data.file_name.absolute_path.name.out + "'")
+ request.add_header ("Content-Type", l_upload_data.content_type)
+ request.set_upload_filename (l_upload_data.file_name.absolute_path.name)
+ request.add_form_parameter("source", l_upload_data.file_name.name.as_string_32)
+ end
+ end
+ end
+
+
+ build_scope: STRING
+ -- Create an string as list of space- delimited, case-sensitive strings.
+ -- https://tools.ietf.org/html/rfc6749#section-3.3
+ do
+ if scopes.is_empty then
+ Result := ""
+ else
+ Result := ""
+ across scopes as ic loop
+ Result.append (ic.item)
+ Result.append_character (' ')
+ end
+ Result.adjust
+ end
+ end
+
+feature -- Service Endpoint
+
+ endpoint_url: STRING
+ -- base URL that specifies the network address of an API service.
+ deferred
+ end
+
+end
+
diff --git a/sheets/src/logger/loggable.e b/gsuite_base/logger/loggable.e
similarity index 100%
rename from sheets/src/logger/loggable.e
rename to gsuite_base/logger/loggable.e
diff --git a/sheets/esheets.ecf b/sheets/esheets.ecf
index ddf095d..a2c5b8d 100644
--- a/sheets/esheets.ecf
+++ b/sheets/esheets.ecf
@@ -18,13 +18,11 @@
-
-
-
-
+
+
diff --git a/sheets/src/eg_sheets_api.e b/sheets/src/eg_sheets_api.e
index 3379197..d8ddef4 100644
--- a/sheets/src/eg_sheets_api.e
+++ b/sheets/src/eg_sheets_api.e
@@ -5,8 +5,13 @@ note
class
EG_SHEETS_API
+
inherit
- LOGGABLE
+
+ EG_COMMON_API
+ rename
+ endpoint_url as endpoint_sheets_url
+ end
create
make
@@ -18,33 +23,17 @@ feature {NONE} -- Initialization
-- Using a code verifier
access_token := a_access_token
enable_version_4
+ default_scope
end
-feature -- Reset
-
- reset
+ default_scope
do
- create access_token.make_empty
+ create {ARRAYED_LIST [STRING_8]} scopes.make (5)
+ add_scope ("https://www.googleapis.com/auth/spreadsheets")
end
-feature -- Access
-
- access_token: STRING_32
- -- Google OAuth2 access token.
-
- http_status: INTEGER
- -- Contains the last HTTP status code returned.
-
- last_api_call: detachable STRING
- -- Contains the last API call.
-
- last_response: detachable OAUTH_RESPONSE
- -- Cointains the last API response.
-
- version: STRING_8
- -- Google Sheets version
-
feature -- Spreedsheets
+
spreadsheet_id: detachable STRING
@@ -62,7 +51,7 @@ feature -- Spreedsheets Operations
then
Result := l_body
end
- end
+ end
get_from_id (a_spreadsheet_id: attached like spreadsheet_id; a_params: detachable EG_SPREADSHEET_PARAMETERS): detachable like last_response.body
-- POST /spreadsheets/`a_spreadsheet_id'
@@ -211,6 +200,28 @@ feature -- Spreedsheets Operations
end
end
+feature -- SpreedSheet URL
+
+ sheets_url (a_query: STRING; a_params: detachable STRING): STRING
+ -- Sheet url endpoint
+ note
+ eis: "name=Sheets service endpoint", "src=https://developers.google.com/sheets/api/reference/rest", "protocol=uri"
+ require
+ a_query_attached: a_query /= Void
+ do
+ create Result.make_from_string (endpoint_sheets_url)
+ Result.append ("/")
+ Result.append (version)
+ Result.append ("/")
+ Result.append (a_query)
+ if attached a_params then
+ Result.append_character ('?')
+ Result.append (a_params)
+ end
+ ensure
+ Result_attached: Result /= Void
+ end
+
feature -- Parameters Factory
parameters (a_params: detachable STRING_TABLE [STRING] ): detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]]
@@ -235,75 +246,6 @@ feature -- Parameters Factory
end
end
-feature -- Error Report
-
- parse_last_response
- require
- attached last_response
- local
- l_json_parser: JSON_PARSER
- do
- check
- attached last_response as l_response
- then
- if l_response.status = {HTTP_STATUS_CODE}.unauthorized then
- logger.write_error ("parse_last_response->Unauthorized status, review your authorization credentials")
- end
- if attached l_response.body as l_body then
- logger.write_debug ("parse_last_response->body:" + l_body)
- create l_json_parser.make_with_string (l_body)
- l_json_parser.parse_content
- if l_json_parser.is_valid then
- if attached {JSON_OBJECT} l_json_parser.parsed_json_value as l_main_jso then
- if attached {JSON_OBJECT} l_main_jso.item ("error") as l_error_jso then
- if attached {JSON_NUMBER} l_error_jso.item ("code") as l_jso then
- print ("parse_last_response-> error code:" + l_jso.representation + "%N")
- end
- if attached {JSON_STRING} l_error_jso.item ("message") as l_jso then
- print ("parse_last_response-> error message:" + l_jso.unescaped_string_8 + "%N")
- end
- if attached {JSON_STRING} l_error_jso.item ("status") as l_jso then
- print ("parse_last_response-> error status:" + l_jso.unescaped_string_8 + "%N")
- end
- end
- end
- else
- print ("parse_last_response-> Error: Invalid json body content:" + l_body + "%N")
- end
- end
- end
- end
-
- has_error: BOOLEAN
- -- Last api call raise an error?
- do
- if attached last_response as l_response then
- Result := l_response.status /= 200
- else
- Result := False
- end
- end
-
- error_message: STRING
- -- Last api call error message.
- require
- has_error: has_error
- do
- if
- attached last_response as l_response
- then
- if
- attached l_response.error_message as l_error_message
- then
- Result := l_error_message
- else
- Result := l_response.status.out
- end
- else
- Result := "Unknown Error"
- end
- end
-
feature -- Versions
enable_version_4
@@ -314,201 +256,15 @@ feature -- Versions
version_set: version.same_string ("v4")
end
-feature {NONE} -- Implementation
-
- api_post_call (a_api_url: STRING; a_params: detachable STRING_TABLE [STRING]; a_payload: detachable STRING; a_upload_data: detachable TUPLE[data:PATH; content_type: STRING])
- -- POST REST API call for `a_api_url'
- do
- internal_api_call (a_api_url, "POST", a_params, a_payload, a_upload_data)
- end
-
- api_delete_call (a_api_url: STRING; a_params: detachable STRING_TABLE [STRING])
- -- DELETE REST API call for `a_api_url'
- do
- internal_api_call (a_api_url, "DELETE", a_params, Void, Void)
- end
-
- api_get_call (a_api_url: STRING; a_params: detachable STRING_TABLE [STRING])
- -- GET REST API call for `a_api_url'
- do
- internal_api_call (a_api_url, "GET", a_params, Void, Void)
- end
-
- internal_api_call (a_api_url: STRING; a_method: STRING; a_params: detachable STRING_TABLE [STRING]; a_payload: detachable STRING; a_upload_data: detachable TUPLE[filename:PATH; content_type: STRING])
- note
- EIS:"name=access token", "src=https://developers.google.com/identity/protocols/oauth2", "protocol=uri"
- local
- request: OAUTH_REQUEST
- l_access_token: detachable OAUTH_TOKEN
- api_service: OAUTH_20_SERVICE
- config: OAUTH_CONFIG
- do
- logger.write_debug ("internal_api_call-> a_api_url:" + a_api_url + " method:" + a_method)
- -- TODO improve this, so we can check the required scopes before we
- -- do an api call.
- -- TODO add a class with the valid scopes.
- create config.make_default ("", "")
- config.set_scope ("https://www.googleapis.com/auth/spreadsheets")
-
- -- Initialization
-
- create api_service.make (create {OAUTH_20_GOOGLE_API}, config)
- --| TODO improve cypress service creation procedure to make configuration optional.
-
- --| TODO rewrite prints as logs
- logger.write_debug ("%N===Google OAuth Workflow using OAuth access token for the owner of the application ===%N")
-
- -- Create the access token that will identifies the user making the request.
- create l_access_token.make_token_secret (access_token, "NOT_NEEDED")
- --| Todo improve access_token to create a token without a secret.
- check attached l_access_token as ll_access_token then
- logger.write_information ("internal_api_call->Got the Access Token:" + ll_access_token.token);
-
- --Now let's go and check if the request is signed correcty
- logger.write_information ("internal_api_call->Now we're going to verify our credentials...%N");
- -- Build the request and authorize it with OAuth.
- create request.make (a_method, a_api_url)
- -- Workaorund to make it work with Google API
- -- in other case it return HTTP 411 Length Required.
- -- Todo check.
- upload_data (a_method, request, a_upload_data)
- add_parameters (a_method, request, a_params)
- -- adding payload
- if attached a_payload then
- request.add_header ("Content-length", a_payload.count.out)
- request.add_header ("Content-Type", "application/json; charset=UTF-8")
- request.add_payload (a_payload)
- else
- request.add_header ("Content-length", "0")
- end
-
- api_service.sign_request (ll_access_token, request)
-
- logger.write_debug ("internal_api_call->uri:'" + request.uri + "'")
- if attached request.upload_file as l_s then
- logger.write_debug ("internal_api_call->upload file:'" + l_s.out + "'")
- end
- if attached {OAUTH_RESPONSE} request.execute as l_response then
- last_response := l_response
- end
- end
- last_api_call := a_api_url.string
- end
-
- sheets_url (a_query: STRING; a_params: detachable STRING): STRING
- -- Sheet url endpoint
- note
- eis: "name=Sheets service endpoint", "src=https://developers.google.com/sheets/api/reference/rest", "protocol=uri"
- require
- a_query_attached: a_query /= Void
- do
- create Result.make_from_string (endpooint_sheets_url)
- Result.append ("/")
- Result.append (version)
- Result.append ("/")
- Result.append (a_query)
- if attached a_params then
- Result.append_character ('?')
- Result.append (a_params)
- end
- ensure
- Result_attached: Result /= Void
- end
-
- url (a_base_url: STRING; a_parameters: detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]]): STRING
- -- url for `a_base_url' and `a_parameters'
- require
- a_base_url_attached: a_base_url /= Void
- do
- create Result.make_from_string (a_base_url)
- append_parameters_to_url (Result, a_parameters)
- end
- append_parameters_to_url (a_url: STRING; a_parameters: detachable ARRAY [detachable TUPLE [name: STRING; value: STRING]])
- -- Append parameters `a_parameters' to `a_url'
- require
- a_url_attached: a_url /= Void
- local
- i: INTEGER
- l_first_param: BOOLEAN
- do
- if a_parameters /= Void and then a_parameters.count > 0 then
- if a_url.index_of ('?', 1) > 0 then
- l_first_param := False
- elseif a_url.index_of ('&', 1) > 0 then
- l_first_param := False
- else
- l_first_param := True
- end
- from
- i := a_parameters.lower
- until
- i > a_parameters.upper
- loop
- if attached a_parameters[i] as a_param then
- if l_first_param then
- a_url.append_character ('?')
- else
- a_url.append_character ('&')
- end
- a_url.append_string (a_param.name)
- a_url.append_character ('=')
- a_url.append_string (a_param.value)
- l_first_param := False
- end
- i := i + 1
- end
- end
- end
-
- add_parameters (a_method: STRING; request:OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
- -- add parameters 'a_params' (with key, value) to the oauth request 'request'.
- --| at the moment all params are added to the query_string.
- do
- add_query_parameters (request, a_params)
- end
-
- add_query_parameters (request:OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
- do
- if attached a_params then
- across a_params as ic
- loop
- request.add_query_string_parameter (ic.key.as_string_8, ic.item)
- end
- end
- end
-
- add_body_parameters (request:OAUTH_REQUEST; a_params: detachable STRING_TABLE [STRING])
- do
- if attached a_params then
- across a_params as ic
- loop
- request.add_body_parameter (ic.key.as_string_8, ic.item)
- end
- end
- end
+feature -- Service Endpoint
- upload_data (a_method: STRING; request:OAUTH_REQUEST; a_upload_data: detachable TUPLE[file_name:PATH; content_type: STRING])
- local
- l_raw_file: RAW_FILE
+ endpoint_sheets_url: STRING
+ --
do
- if a_method.is_case_insensitive_equal_general ("POST") and then attached a_upload_data as l_upload_data then
- create l_raw_file.make_open_read (l_upload_data.file_name.absolute_path.name)
- if l_raw_file.exists then
- logger.write_debug ("upload_data-> Content-type: '" + l_upload_data.content_type + "'")
- logger.write_debug ("upload_data-> upload file name: '" + l_upload_data.file_name.absolute_path.name.out + "'")
- request.add_header ("Content-Type", l_upload_data.content_type)
- request.set_upload_filename (l_upload_data.file_name.absolute_path.name)
- request.add_form_parameter("source", l_upload_data.file_name.name.as_string_32)
- end
- end
+ Result := "https://sheets.googleapis.com"
end
-feature -- Service Endpoint
-
- endpooint_sheets_url: STRING = "https://sheets.googleapis.com"
- -- base URL that specifies the network address of an API service.
-
feature {NONE} -- Implementation
data_file: detachable PLAIN_TEXT_FILE
diff --git a/sheets/test/test.ecf b/sheets/test/test.ecf
index 46d1006..743dea7 100644
--- a/sheets/test/test.ecf
+++ b/sheets/test/test.ecf
@@ -1,5 +1,5 @@
-
+
/CVS$
@@ -7,7 +7,7 @@
/\.git$
/\.svn$
-
@@ -20,11 +20,11 @@
-
+
-
@@ -32,7 +32,7 @@
-
diff --git a/sheets/test/test_sheets_api.e b/sheets/test/test_sheets_api.e
index 9bf3199..1b3b87e 100644
--- a/sheets/test/test_sheets_api.e
+++ b/sheets/test/test_sheets_api.e
@@ -10,6 +10,7 @@ inherit
APPLICATION_FLOW
+
create
make
@@ -20,13 +21,14 @@ feature -- {NONE}
-- TODO improve this code so we can select which integration test we want to run.
logger.write_information ("make-> ======================> Starting application")
set_from_json_credentials_file_path (create {PATH}.make_from_string (credentials_path))
+
retrieve_access_token
test_create_sheet
---- test_get_sheet ("1v1N4nRa6mmLcP9rUuyQPiCnLuUcBQFDEC7E0CDg3ASI")
--- test_append_sheet ("19cKCmQBWJoMePX0Iy6LueHRw0sS2bMcyP1Auzbkvj6M", impl_append_post_data_sample) --pg
+ test_append_sheet ("19cKCmQBWJoMePX0Iy6LueHRw0sS2bMcyP1Auzbkvj6M", impl_append_post_data_sample) --pg
-- --test_append_sheet ("1j5CTkpgOc6Y5qgYdA_klZYjNhmN2KYocoZAdM4Y61tw") --jv
----- set_from_json_credentials_file_path (create {PATH}.make_from_string (CREDENTIALS_PATH))
+---- set_from_json_credentials_file_path (create {PATH}.make_from_string (CREDENTIALS_PATH))
-- retrieve_access_token
-- test_get_sheet ("1v1N4nRa6mmLcP9rUuyQPiCnLuUcBQFDEC7E0CDg3ASI")
end
@@ -34,7 +36,8 @@ feature -- {NONE}
feature -- Tests
- test_create_sheet
+
+ test_create_sheet
require
token_is_valid
local
@@ -151,11 +154,10 @@ feature -- Tests
feature {NONE} -- Implementations
- CREDENTIALS_PATH: STRING="credentials.json" -- get this file from https://console.developers.google.com/
+ CREDENTIALS_PATH: STRING="credentials.json" -- get this file from https://console.developers.google.com/
-- Credentials path to json file.
-
impl_append_post_data_sample: STRING
local
l_res: JSON_OBJECT