Merge pull request #43 from MycroftAI/account-api
added account profile endpoint to account APIpull/46/head
commit
0d54672b10
|
@ -0,0 +1,17 @@
|
|||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
flask = "*"
|
||||
flask-restful = "*"
|
||||
uwsgi = "*"
|
||||
|
||||
[dev-packages]
|
||||
selene = {editable = true,path = "./../../shared"}
|
||||
behave = "*"
|
||||
pyhamcrest = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
|
@ -0,0 +1,361 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7d934739849ec9eb381b562adad7e4842dba13f11cad2ded35e154163bb4876b"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aniso8601": {
|
||||
"hashes": [
|
||||
"sha256:03c0ffeeb04edeca1ed59684cc6836dc377f58e52e315dc7be3af879909889f4",
|
||||
"sha256:ac30cceff24aec920c37b8d74d7d8a5dd37b1f62a90b4f268a6234cabe147080"
|
||||
],
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"flask-restful": {
|
||||
"hashes": [
|
||||
"sha256:ecd620c5cc29f663627f99e04f17d1f16d095c83dc1d618426e2ad68b03092f8",
|
||||
"sha256:f8240ec12349afe8df1db168ea7c336c4e5b0271a36982bff7394f93275f2ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.7"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
|
||||
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
|
||||
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
|
||||
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
|
||||
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
|
||||
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
|
||||
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
|
||||
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
|
||||
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
|
||||
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
|
||||
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
|
||||
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
|
||||
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
|
||||
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
|
||||
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
|
||||
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
|
||||
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
|
||||
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
|
||||
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
|
||||
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
|
||||
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
|
||||
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
|
||||
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
|
||||
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
|
||||
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
|
||||
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
|
||||
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
|
||||
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
||||
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
||||
],
|
||||
"version": "==2018.9"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"uwsgi": {
|
||||
"hashes": [
|
||||
"sha256:d2318235c74665a60021a4fc7770e9c2756f9fc07de7b8c22805efe85b5ab277"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.17.1"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"aniso8601": {
|
||||
"hashes": [
|
||||
"sha256:03c0ffeeb04edeca1ed59684cc6836dc377f58e52e315dc7be3af879909889f4",
|
||||
"sha256:ac30cceff24aec920c37b8d74d7d8a5dd37b1f62a90b4f268a6234cabe147080"
|
||||
],
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"behave": {
|
||||
"hashes": [
|
||||
"sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86",
|
||||
"sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.6"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||
],
|
||||
"version": "==2018.11.29"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"deprecated": {
|
||||
"hashes": [
|
||||
"sha256:8bfeba6e630abf42b5d111b68a05f7fe3d6de7004391b3cd614947594f87a4ff",
|
||||
"sha256:b784e0ca85a8c1e694d77e545c10827bd99772392e79d5f5442e761515a1246e"
|
||||
],
|
||||
"version": "==1.2.4"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"flask-restful": {
|
||||
"hashes": [
|
||||
"sha256:ecd620c5cc29f663627f99e04f17d1f16d095c83dc1d618426e2ad68b03092f8",
|
||||
"sha256:f8240ec12349afe8df1db168ea7c336c4e5b0271a36982bff7394f93275f2ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.7"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
|
||||
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
|
||||
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
|
||||
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
|
||||
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
|
||||
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
|
||||
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
|
||||
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
|
||||
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
|
||||
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
|
||||
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
|
||||
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
|
||||
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
|
||||
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
|
||||
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
|
||||
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
|
||||
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
|
||||
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
|
||||
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
|
||||
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
|
||||
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
|
||||
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
|
||||
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
|
||||
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
|
||||
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
|
||||
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
|
||||
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
|
||||
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"parse": {
|
||||
"hashes": [
|
||||
"sha256:870dd675c1ee8951db3e29b81ebe44fd131e3eb8c03a79483a58ea574f3145c2"
|
||||
],
|
||||
"version": "==1.11.1"
|
||||
},
|
||||
"parse-type": {
|
||||
"hashes": [
|
||||
"sha256:6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9",
|
||||
"sha256:f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c"
|
||||
],
|
||||
"version": "==0.4.2"
|
||||
},
|
||||
"passlib": {
|
||||
"hashes": [
|
||||
"sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0",
|
||||
"sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280"
|
||||
],
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:19a2d1f3567b30f6c2bb3baea23f74f69d51f0c06c2e2082d0d9c28b0733a4c2",
|
||||
"sha256:2b69cf4b0fa2716fd977aa4e1fd39af6110eb47b2bb30b4e5a469d8fbecfc102",
|
||||
"sha256:2e952fa17ba48cbc2dc063ddeec37d7dc4ea0ef7db0ac1eda8906365a8543f31",
|
||||
"sha256:348b49dd737ff74cfb5e663e18cb069b44c64f77ec0523b5794efafbfa7df0b8",
|
||||
"sha256:3d72a5fdc5f00ca85160915eb9a973cf9a0ab8148f6eda40708bf672c55ac1d1",
|
||||
"sha256:4957452f7868f43f32c090dadb4188e9c74a4687323c87a882e943c2bd4780c3",
|
||||
"sha256:5138cec2ee1e53a671e11cc519505eb08aaaaf390c508f25b09605763d48de4b",
|
||||
"sha256:587098ca4fc46c95736459d171102336af12f0d415b3b865972a79c03f06259f",
|
||||
"sha256:5b79368bcdb1da4a05f931b62760bea0955ee2c81531d8e84625df2defd3f709",
|
||||
"sha256:5cf43807392247d9bc99737160da32d3fa619e0bfd85ba24d1c78db205f472a4",
|
||||
"sha256:676d1a80b1eebc0cacae8dd09b2fde24213173bf65650d22b038c5ed4039f392",
|
||||
"sha256:6b0211ecda389101a7d1d3df2eba0cf7ffbdd2480ca6f1d2257c7bd739e84110",
|
||||
"sha256:79cde4660de6f0bb523c229763bd8ad9a93ac6760b72c369cf1213955c430934",
|
||||
"sha256:7aba9786ac32c2a6d5fb446002ed936b47d5e1f10c466ef7e48f66eb9f9ebe3b",
|
||||
"sha256:7c8159352244e11bdd422226aa17651110b600d175220c451a9acf795e7414e0",
|
||||
"sha256:945f2eedf4fc6b2432697eb90bb98cc467de5147869e57405bfc31fa0b824741",
|
||||
"sha256:96b4e902cde37a7fc6ab306b3ac089a3949e6ce3d824eeca5b19dc0bedb9f6e2",
|
||||
"sha256:9a7bccb1212e63f309eb9fab47b6eaef796f59850f169a25695b248ca1bf681b",
|
||||
"sha256:a3bfcac727538ec11af304b5eccadbac952d4cca1a551a29b8fe554e3ad535dc",
|
||||
"sha256:b19e9f1b85c5d6136f5a0549abdc55dcbd63aba18b4f10d0d063eb65ef2c68b4",
|
||||
"sha256:b664011bb14ca1f2287c17185e222f2098f7b4c857961dbcf9badb28786dbbf4",
|
||||
"sha256:bde7959ef012b628868d69c474ec4920252656d0800835ed999ba5e4f57e3e2e",
|
||||
"sha256:cb095a0657d792c8de9f7c9a0452385a309dfb1bbbb3357d6b1e216353ade6ca",
|
||||
"sha256:d16d42a1b9772152c1fe606f679b2316551f7e1a1ce273e7f808e82a136cdb3d",
|
||||
"sha256:d444b1545430ffc1e7a24ce5a9be122ccd3b135a7b7e695c5862c5aff0b11159",
|
||||
"sha256:d93ccc7bf409ec0a23f2ac70977507e0b8a8d8c54e5ee46109af2f0ec9e411f3",
|
||||
"sha256:df6444f952ca849016902662e1a47abf4fa0678d75f92fd9dd27f20525f809cd",
|
||||
"sha256:e63850d8c52ba2b502662bf3c02603175c2397a9acc756090e444ce49508d41e",
|
||||
"sha256:ec43358c105794bc2b6fd34c68d27f92bea7102393c01889e93f4b6a70975728",
|
||||
"sha256:f4c6926d9c03dadce7a3b378b40d2fea912c1344ef9b29869f984fb3d2a2420b"
|
||||
],
|
||||
"version": "==2.7.7"
|
||||
},
|
||||
"pygithub": {
|
||||
"hashes": [
|
||||
"sha256:263102b43a83e2943900c1313109db7a00b3b78aeeae2c9137ba694982864872"
|
||||
],
|
||||
"version": "==1.43.5"
|
||||
},
|
||||
"pyhamcrest": {
|
||||
"hashes": [
|
||||
"sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420",
|
||||
"sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e",
|
||||
"sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"
|
||||
],
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
||||
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
||||
],
|
||||
"version": "==2018.9"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||
],
|
||||
"version": "==2.21.0"
|
||||
},
|
||||
"selene": {
|
||||
"editable": true,
|
||||
"path": "./../../shared"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||
],
|
||||
"version": "==1.24.1"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533"
|
||||
],
|
||||
"version": "==1.11.1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
"""Entry point for the API that supports the Mycroft Marketplace."""
|
||||
from flask import Flask
|
||||
from flask_restful import Api
|
||||
|
||||
from selene.api import AccountEndpoint, get_base_config
|
||||
from selene.api import JSON_MIMETYPE, output_json
|
||||
|
||||
# Define the Flask application
|
||||
acct = Flask(__name__)
|
||||
acct.config.from_object(get_base_config())
|
||||
|
||||
# Define the API and its endpoints.
|
||||
acct_api = Api(acct)
|
||||
acct_api.representations[JSON_MIMETYPE] = output_json
|
||||
acct_api.add_resource(AccountEndpoint, '/api/account')
|
|
@ -0,0 +1,55 @@
|
|||
from datetime import date
|
||||
import os
|
||||
|
||||
from behave import fixture, use_fixture
|
||||
|
||||
from account_api.api import acct
|
||||
from selene.data.account import (
|
||||
Account,
|
||||
AccountAgreement,
|
||||
AccountRepository,
|
||||
AccountSubscription
|
||||
)
|
||||
from selene.util.db import get_db_connection
|
||||
|
||||
|
||||
@fixture
|
||||
def acct_api_client(context):
|
||||
acct.testing = True
|
||||
context.client_config = acct.config
|
||||
context.client = acct.test_client()
|
||||
|
||||
yield context.client
|
||||
|
||||
|
||||
def before_feature(context, _):
|
||||
use_fixture(acct_api_client, context)
|
||||
os.environ['SALT'] = 'testsalt'
|
||||
|
||||
|
||||
def before_scenario(context, _):
|
||||
test_account = Account(
|
||||
id=None,
|
||||
email_address='foo@mycroft.ai',
|
||||
refresh_tokens=None,
|
||||
subscription=AccountSubscription(
|
||||
type='monthly supporter',
|
||||
start_date=None,
|
||||
stripe_customer_id='foo'
|
||||
),
|
||||
agreements=[
|
||||
AccountAgreement(name='terms', signature_date=None)
|
||||
]
|
||||
)
|
||||
with get_db_connection(context.client_config['DB_CONNECTION_POOL']) as db:
|
||||
acct_repository = AccountRepository(db)
|
||||
acct_repository.add(test_account, 'foo')
|
||||
context.account = acct_repository.get_account_by_email(
|
||||
test_account.email_address
|
||||
)
|
||||
|
||||
|
||||
def after_scenario(context, _):
|
||||
with get_db_connection(context.client_config['DB_CONNECTION_POOL']) as db:
|
||||
acct_repository = AccountRepository(db)
|
||||
acct_repository.remove(context.account)
|
|
@ -0,0 +1,8 @@
|
|||
Feature: Manage account profiles
|
||||
Test the ability of the account API to retrieve and manage a user's profile
|
||||
settings.
|
||||
|
||||
Scenario: Retrieve authenticated user's account
|
||||
Given an authenticated user
|
||||
When account endpoint is called to get user profile
|
||||
Then user profile is returned
|
|
@ -0,0 +1,35 @@
|
|||
from datetime import date
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
|
||||
from behave import given, then, when
|
||||
from hamcrest import assert_that, equal_to
|
||||
|
||||
from selene.api.testing import generate_auth_tokens
|
||||
|
||||
|
||||
@given('an authenticated user')
|
||||
def setup_authenticated_user(context):
|
||||
generate_auth_tokens(context)
|
||||
|
||||
|
||||
@when('account endpoint is called to get user profile')
|
||||
def call_account_endpoint(context):
|
||||
context.response = context.client.get('/api/account')
|
||||
|
||||
|
||||
@then('user profile is returned')
|
||||
def validate_response(context):
|
||||
assert_that(context.response.status_code, equal_to(HTTPStatus.OK))
|
||||
response_data = json.loads(context.response.data)
|
||||
assert_that(
|
||||
response_data['emailAddress'],
|
||||
equal_to(context.account.email_address)
|
||||
)
|
||||
assert_that(response_data['id'], equal_to(context.account.id))
|
||||
assert_that(response_data['subscription'], equal_to(
|
||||
dict(type='monthly supporter', startDate=str(date.today()))
|
||||
))
|
||||
assert_that(response_data['agreements'], equal_to(
|
||||
[dict(name='terms', signatureDate=str(date.today()))]
|
||||
))
|
|
@ -3,7 +3,12 @@ import os
|
|||
from behave import fixture, use_fixture
|
||||
|
||||
from sso_api.api import sso
|
||||
from selene.data.account import AccountRepository
|
||||
from selene.data.account import (
|
||||
Account,
|
||||
AccountAgreement,
|
||||
AccountRepository,
|
||||
AccountSubscription
|
||||
)
|
||||
from selene.util.db import get_db_connection
|
||||
|
||||
|
||||
|
@ -23,11 +28,26 @@ def before_feature(context, _):
|
|||
|
||||
|
||||
def before_scenario(context, _):
|
||||
with get_db_connection(context.db_pool) as db:
|
||||
test_account = Account(
|
||||
id=None,
|
||||
email_address='foo@mycroft.ai',
|
||||
refresh_tokens=None,
|
||||
subscription=AccountSubscription(
|
||||
type='monthly supporter',
|
||||
start_date=None,
|
||||
stripe_customer_id='foo'
|
||||
),
|
||||
agreements=[
|
||||
AccountAgreement(name='terms', signature_date=None)
|
||||
]
|
||||
)
|
||||
with get_db_connection(
|
||||
context.client_config['DB_CONNECTION_POOL']) as db:
|
||||
acct_repository = AccountRepository(db)
|
||||
account_id = acct_repository.add('foo@mycroft.ai', 'foo')
|
||||
account = acct_repository.get_account_by_id(account_id)
|
||||
context.account = account
|
||||
acct_repository.add(test_account, 'foo')
|
||||
context.account = acct_repository.get_account_by_email(
|
||||
test_account.email_address
|
||||
)
|
||||
|
||||
|
||||
def after_scenario(context, _):
|
||||
|
|
|
@ -2,13 +2,11 @@ from http import HTTPStatus
|
|||
from behave import given, then, when
|
||||
from hamcrest import assert_that, equal_to, has_item, is_not
|
||||
|
||||
from selene.data.account import RefreshTokenRepository
|
||||
from selene.api.testing import get_account, validate_token_cookies
|
||||
from selene.util.auth import AuthenticationTokenGenerator
|
||||
from selene.util.db import get_db_connection
|
||||
|
||||
ACCESS_TOKEN_COOKIE_KEY = 'seleneAccess'
|
||||
REFRESH_TOKEN_COOKIE_KEY = 'seleneRefresh'
|
||||
from selene.api.testing import (
|
||||
generate_auth_tokens,
|
||||
get_account,
|
||||
validate_token_cookies
|
||||
)
|
||||
|
||||
|
||||
@given('user "{email}" is authenticated')
|
||||
|
@ -18,26 +16,7 @@ def save_email(context, email):
|
|||
|
||||
@when('user attempts to logout')
|
||||
def call_logout_endpoint(context):
|
||||
token_generator = AuthenticationTokenGenerator(
|
||||
context.account.id,
|
||||
context.client_config['ACCESS_SECRET'],
|
||||
context.client_config['REFRESH_SECRET']
|
||||
)
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
ACCESS_TOKEN_COOKIE_KEY,
|
||||
token_generator.access_token
|
||||
)
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
token_generator.refresh_token
|
||||
)
|
||||
context.request_refresh_token = token_generator.refresh_token
|
||||
with get_db_connection(context.client_config['DB_CONNECTION_POOL']) as db:
|
||||
token_repository = RefreshTokenRepository(db, context.account)
|
||||
token_repository.add_refresh_token(token_generator.refresh_token)
|
||||
|
||||
generate_auth_tokens(context)
|
||||
context.response = context.client.get('/api/logout')
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ flask-restful = "*"
|
|||
psycopg2-binary = "*"
|
||||
passlib = "*"
|
||||
pyhamcrest = "*"
|
||||
validator-collection = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "c76a19553967e3b2b049bb52a83cf9b15ea4a298b53a8e9bb7ab9244ce11fc83"
|
||||
"sha256": "c2bf160284a4d0bf594cf4cda88470b39cf188b80b9340a4fcbd127cdbd1700b"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -87,6 +87,13 @@
|
|||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"jsonschema": {
|
||||
"hashes": [
|
||||
"sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08",
|
||||
"sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
|
||||
|
@ -215,6 +222,13 @@
|
|||
],
|
||||
"version": "==1.24.1"
|
||||
},
|
||||
"validator-collection": {
|
||||
"hashes": [
|
||||
"sha256:1008f31ead2271e5caf1b655e1605ff42fde2b39620be7953558b58d5d8f1325"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
from .account_endpoint import AccountEndpoint
|
||||
from .base_endpoint import APIError, SeleneEndpoint
|
||||
from .base_config import get_base_config
|
||||
from .response_data_formatter import JSON_MIMETYPE, output_json
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
"""API endpoint to return the a logged-in user's profile"""
|
||||
from dataclasses import asdict
|
||||
from http import HTTPStatus
|
||||
|
||||
from .base_endpoint import SeleneEndpoint
|
||||
|
||||
|
||||
class AccountEndpoint(SeleneEndpoint):
|
||||
"""Retrieve information about the user based on their UUID"""
|
||||
|
||||
def get(self):
|
||||
"""Process HTTP GET request for an account."""
|
||||
self._authenticate()
|
||||
if self.authenticated:
|
||||
self._build_response()
|
||||
|
||||
return self.response
|
||||
|
||||
def _build_response(self):
|
||||
"""Build the response to the user info request."""
|
||||
response_data = asdict(self.account)
|
||||
del(response_data['refresh_tokens'])
|
||||
self.response = (response_data, HTTPStatus.OK)
|
|
@ -1 +1,7 @@
|
|||
from .authentication import get_account, validate_token_cookies
|
||||
from .authentication import (
|
||||
ACCESS_TOKEN_COOKIE_KEY,
|
||||
generate_auth_tokens,
|
||||
get_account,
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
validate_token_cookies
|
||||
)
|
||||
|
|
|
@ -1,12 +1,40 @@
|
|||
from hamcrest import assert_that, equal_to, has_item
|
||||
|
||||
from selene.data.account import Account, AccountRepository
|
||||
from selene.data.account import (
|
||||
Account,
|
||||
AccountRepository,
|
||||
RefreshTokenRepository
|
||||
)
|
||||
from selene.util.auth import AuthenticationTokenGenerator
|
||||
from selene.util.db import get_db_connection
|
||||
|
||||
ACCESS_TOKEN_COOKIE_KEY = 'seleneAccess'
|
||||
REFRESH_TOKEN_COOKIE_KEY = 'seleneRefresh'
|
||||
|
||||
|
||||
def generate_auth_tokens(context):
|
||||
token_generator = AuthenticationTokenGenerator(
|
||||
context.account.id,
|
||||
context.client_config['ACCESS_SECRET'],
|
||||
context.client_config['REFRESH_SECRET']
|
||||
)
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
ACCESS_TOKEN_COOKIE_KEY,
|
||||
token_generator.access_token
|
||||
)
|
||||
context.client.set_cookie(
|
||||
context.client_config['DOMAIN'],
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
token_generator.refresh_token
|
||||
)
|
||||
context.request_refresh_token = token_generator.refresh_token
|
||||
|
||||
with get_db_connection(context.client_config['DB_CONNECTION_POOL']) as db:
|
||||
token_repository = RefreshTokenRepository(db, context.account)
|
||||
token_repository.add_refresh_token(token_generator.refresh_token)
|
||||
|
||||
|
||||
def validate_token_cookies(context, expired=False):
|
||||
for cookie in context.response.headers.getlist('Set-Cookie'):
|
||||
ingredients = _parse_cookie(cookie)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
from .entity.account import Account
|
||||
from .entity.account import Account, AccountAgreement, AccountSubscription
|
||||
from .repository.account import AccountRepository
|
||||
from .repository.refresh_token import RefreshTokenRepository
|
||||
|
|
|
@ -2,14 +2,24 @@ from datetime import date
|
|||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from validator_collection import validators
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountAgreement(object):
|
||||
"""Representation of a 'signed' agreement"""
|
||||
agreement: str
|
||||
name: str
|
||||
signature_date: date
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountSubscription(object):
|
||||
"""Represents the subscription plan chosen by the user"""
|
||||
type: str
|
||||
start_date: date
|
||||
stripe_customer_id: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Account(object):
|
||||
"""Representation of a Mycroft user account."""
|
||||
|
@ -17,4 +27,7 @@ class Account(object):
|
|||
email_address: str
|
||||
refresh_tokens: List[str]
|
||||
agreements: List[AccountAgreement]
|
||||
subscription: str
|
||||
subscription: AccountSubscription
|
||||
|
||||
def __post_init__(self):
|
||||
self.email_address = validators.email(self.email_address)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
from passlib.hash import sha512_crypt
|
||||
from os import environ, path
|
||||
|
||||
from selene.util.db import DatabaseRequest, Cursor, get_sql_from_file
|
||||
from selene.util.db import (
|
||||
DatabaseRequest,
|
||||
Cursor,
|
||||
get_sql_from_file,
|
||||
use_transaction
|
||||
)
|
||||
from ..entity.account import Account
|
||||
|
||||
SQL_DIR = path.join(path.dirname(__file__), 'sql')
|
||||
|
@ -18,25 +23,64 @@ def _encrypt_password(password):
|
|||
class AccountRepository(object):
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
self.cursor = Cursor(db)
|
||||
|
||||
def add(self, email_address: str, password: str) -> str:
|
||||
@use_transaction
|
||||
def add(self, account: Account, password: str):
|
||||
account.id = self._add_account(account, password)
|
||||
self._add_agreement(account)
|
||||
if account.subscription is not None:
|
||||
self._add_subscription(account)
|
||||
|
||||
def _add_account(self, account: Account, password: str):
|
||||
"""Add a row to the account table."""
|
||||
encrypted_password = _encrypt_password(password)
|
||||
request = DatabaseRequest(
|
||||
sql=get_sql_from_file(path.join(SQL_DIR, 'add_account.sql')),
|
||||
args=dict(email_address=email_address, password=encrypted_password)
|
||||
args=dict(
|
||||
email_address=account.email_address,
|
||||
password=encrypted_password
|
||||
)
|
||||
)
|
||||
cursor = Cursor(self.db)
|
||||
result = cursor.insert_returning(request)
|
||||
result = self.cursor.insert_returning(request)
|
||||
|
||||
return result['id']
|
||||
|
||||
def _add_agreement(self, account: Account):
|
||||
"""Accounts cannot be added without agreeing to terms and privacy"""
|
||||
for agreement in account.agreements:
|
||||
request = DatabaseRequest(
|
||||
sql=get_sql_from_file(
|
||||
path.join(SQL_DIR, 'add_account_agreement.sql')
|
||||
),
|
||||
args=dict(
|
||||
account_id=account.id,
|
||||
agreement_name=agreement.name
|
||||
)
|
||||
)
|
||||
self.cursor.insert(request)
|
||||
|
||||
def _add_subscription(self, account: Account):
|
||||
"""A subscription is optional, add it if one was selected"""
|
||||
request = DatabaseRequest(
|
||||
sql=get_sql_from_file(
|
||||
path.join(SQL_DIR, 'add_account_subscription.sql')
|
||||
),
|
||||
args=dict(
|
||||
account_id=account.id,
|
||||
subscription_type=account.subscription.type,
|
||||
stripe_customer_id=account.subscription.stripe_customer_id
|
||||
)
|
||||
)
|
||||
self.cursor.insert(request)
|
||||
|
||||
def remove(self, account: Account):
|
||||
"""Delete and account and all of its children"""
|
||||
request = DatabaseRequest(
|
||||
sql=get_sql_from_file(path.join(SQL_DIR, 'remove_account.sql')),
|
||||
args=dict(id=account.id)
|
||||
)
|
||||
cursor = Cursor(self.db)
|
||||
cursor.delete(request)
|
||||
self.cursor.delete(request)
|
||||
|
||||
def get_account_by_id(self, account_id: str) -> Account:
|
||||
"""Use a given uuid to query the database for an account
|
||||
|
@ -94,8 +138,7 @@ class AccountRepository(object):
|
|||
|
||||
def _get_account(self, db_request):
|
||||
account = None
|
||||
cursor = Cursor(self.db)
|
||||
result = cursor.select_one(db_request)
|
||||
result = self.cursor.select_one(db_request)
|
||||
|
||||
if result is not None:
|
||||
account = Account(**result['account'])
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
INSERT INTO
|
||||
account.account_agreement (account_id, agreement_id, agreement_ts_range)
|
||||
VALUES
|
||||
(
|
||||
%(account_id)s,
|
||||
(
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
account.agreement
|
||||
WHERE
|
||||
agreement = %(agreement_name)s
|
||||
),
|
||||
'[now,]'
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
INSERT INTO
|
||||
account.account_subscription (
|
||||
account_id,
|
||||
subscription_id,
|
||||
subscription_ts_range,
|
||||
stripe_customer_id
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
%(account_id)s,
|
||||
(
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
account.subscription
|
||||
WHERE
|
||||
subscription = %(subscription_type)s
|
||||
),
|
||||
'[now,]',
|
||||
%(stripe_customer_id)s
|
||||
)
|
|
@ -11,7 +11,7 @@ WITH
|
|||
SELECT
|
||||
array_agg(
|
||||
json_build_object(
|
||||
'agreement', ag.agreement,
|
||||
'name', ag.agreement,
|
||||
'signature_date', lower(aa.agreement_ts_range)::DATE
|
||||
)
|
||||
)
|
||||
|
@ -24,7 +24,10 @@ WITH
|
|||
),
|
||||
subscription AS (
|
||||
SELECT
|
||||
s.subscription
|
||||
json_build_object(
|
||||
'type', s.subscription,
|
||||
'start_date', lower(asub.subscription_ts_range)::DATE
|
||||
)
|
||||
FROM
|
||||
account.account_subscription asub
|
||||
INNER JOIN account.subscription s ON asub.subscription_id = s.id
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from .connection import DatabaseConnectionConfig
|
||||
from .connection_pool import allocate_db_connection_pool, get_db_connection
|
||||
from .cursor import DatabaseRequest, Cursor, get_sql_from_file
|
||||
from .transaction import use_transaction
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"""Tools for executing sql within a transaction."""
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def use_transaction(func):
|
||||
"""Execute all sql statements within the wrapped function in a transaction
|
||||
|
||||
This is a decorator that assumes the function it is wrapping is a method
|
||||
of a class with a "db" attribute that is a psycopg connection object.
|
||||
|
||||
:param func: function being decorated
|
||||
:return: decorated function
|
||||
"""
|
||||
@wraps(func)
|
||||
def execute_in_transaction(*args, **kwargs):
|
||||
instance = args[0]
|
||||
if hasattr(instance, "db"):
|
||||
prev_autocommit = instance.db.autocommit
|
||||
instance.db.autocommit = False
|
||||
with instance.db:
|
||||
func(*args, **kwargs)
|
||||
instance.db.autocommit = prev_autocommit
|
||||
|
||||
return execute_in_transaction
|
|
@ -16,6 +16,7 @@ setup(
|
|||
'pygithub',
|
||||
'pyhamcrest',
|
||||
'pyjwt',
|
||||
'psycopg2-binary'
|
||||
'psycopg2-binary',
|
||||
'validator-collection'
|
||||
]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue