Added basic auth system, profiles, and start of models and data.
This commit is contained in:
parent
a24ab226c2
commit
e198f45b92
|
@ -14,3 +14,5 @@ finnow-api-test-*
|
|||
*.o
|
||||
*.obj
|
||||
*.lst
|
||||
|
||||
users/
|
||||
|
|
|
@ -0,0 +1,442 @@
|
|||
Entity,Currency,AlphabeticCode,NumericCode,MinorUnit,WithdrawalDate
|
||||
AFGHANISTAN,Afghani,AFN,971,2,
|
||||
ÅLAND ISLANDS,Euro,EUR,978,2,
|
||||
ALBANIA,Lek,ALL,008,2,
|
||||
ALGERIA,Algerian Dinar,DZD,012,2,
|
||||
AMERICAN SAMOA,US Dollar,USD,840,2,
|
||||
ANDORRA,Euro,EUR,978,2,
|
||||
ANGOLA,Kwanza,AOA,973,2,
|
||||
ANGUILLA,East Caribbean Dollar,XCD,951,2,
|
||||
ANTARCTICA,No universal currency,,,,
|
||||
ANTIGUA AND BARBUDA,East Caribbean Dollar,XCD,951,2,
|
||||
ARGENTINA,Argentine Peso,ARS,032,2,
|
||||
ARMENIA,Armenian Dram,AMD,051,2,
|
||||
ARUBA,Aruban Florin,AWG,533,2,
|
||||
AUSTRALIA,Australian Dollar,AUD,036,2,
|
||||
AUSTRIA,Euro,EUR,978,2,
|
||||
AZERBAIJAN,Azerbaijan Manat,AZN,944,2,
|
||||
BAHAMAS (THE),Bahamian Dollar,BSD,044,2,
|
||||
BAHRAIN,Bahraini Dinar,BHD,048,3,
|
||||
BANGLADESH,Taka,BDT,050,2,
|
||||
BARBADOS,Barbados Dollar,BBD,052,2,
|
||||
BELARUS,Belarusian Ruble,BYN,933,2,
|
||||
BELGIUM,Euro,EUR,978,2,
|
||||
BELIZE,Belize Dollar,BZD,084,2,
|
||||
BENIN,CFA Franc BCEAO,XOF,952,0,
|
||||
BERMUDA,Bermudian Dollar,BMD,060,2,
|
||||
BHUTAN,Indian Rupee,INR,356,2,
|
||||
BHUTAN,Ngultrum,BTN,064,2,
|
||||
BOLIVIA (PLURINATIONAL STATE OF),Boliviano,BOB,068,2,
|
||||
BOLIVIA (PLURINATIONAL STATE OF),Mvdol,BOV,984,2,
|
||||
"BONAIRE, SINT EUSTATIUS AND SABA",US Dollar,USD,840,2,
|
||||
BOSNIA AND HERZEGOVINA,Convertible Mark,BAM,977,2,
|
||||
BOTSWANA,Pula,BWP,072,2,
|
||||
BOUVET ISLAND,Norwegian Krone,NOK,578,2,
|
||||
BRAZIL,Brazilian Real,BRL,986,2,
|
||||
BRITISH INDIAN OCEAN TERRITORY (THE),US Dollar,USD,840,2,
|
||||
BRUNEI DARUSSALAM,Brunei Dollar,BND,096,2,
|
||||
BULGARIA,Bulgarian Lev,BGN,975,2,
|
||||
BURKINA FASO,CFA Franc BCEAO,XOF,952,0,
|
||||
BURUNDI,Burundi Franc,BIF,108,0,
|
||||
CABO VERDE,Cabo Verde Escudo,CVE,132,2,
|
||||
CAMBODIA,Riel,KHR,116,2,
|
||||
CAMEROON,CFA Franc BEAC,XAF,950,0,
|
||||
CANADA,Canadian Dollar,CAD,124,2,
|
||||
CAYMAN ISLANDS (THE),Cayman Islands Dollar,KYD,136,2,
|
||||
CENTRAL AFRICAN REPUBLIC (THE),CFA Franc BEAC,XAF,950,0,
|
||||
CHAD,CFA Franc BEAC,XAF,950,0,
|
||||
CHILE,Chilean Peso,CLP,152,0,
|
||||
CHILE,Unidad de Fomento,CLF,990,4,
|
||||
CHINA,Yuan Renminbi,CNY,156,2,
|
||||
CHRISTMAS ISLAND,Australian Dollar,AUD,036,2,
|
||||
COCOS (KEELING) ISLANDS (THE),Australian Dollar,AUD,036,2,
|
||||
COLOMBIA,Colombian Peso,COP,170,2,
|
||||
COLOMBIA,Unidad de Valor Real,COU,970,2,
|
||||
COMOROS (THE),Comorian Franc ,KMF,174,0,
|
||||
CONGO (THE DEMOCRATIC REPUBLIC OF THE),Congolese Franc,CDF,976,2,
|
||||
CONGO (THE),CFA Franc BEAC,XAF,950,0,
|
||||
COOK ISLANDS (THE),New Zealand Dollar,NZD,554,2,
|
||||
COSTA RICA,Costa Rican Colon,CRC,188,2,
|
||||
CÔTE D'IVOIRE,CFA Franc BCEAO,XOF,952,0,
|
||||
CROATIA,Kuna,HRK,191,2,
|
||||
CUBA,Cuban Peso,CUP,192,2,
|
||||
CUBA,Peso Convertible,CUC,931,2,
|
||||
CURAÇAO,Netherlands Antillean Guilder,ANG,532,2,
|
||||
CYPRUS,Euro,EUR,978,2,
|
||||
CZECHIA,Czech Koruna,CZK,203,2,
|
||||
DENMARK,Danish Krone,DKK,208,2,
|
||||
DJIBOUTI,Djibouti Franc,DJF,262,0,
|
||||
DOMINICA,East Caribbean Dollar,XCD,951,2,
|
||||
DOMINICAN REPUBLIC (THE),Dominican Peso,DOP,214,2,
|
||||
ECUADOR,US Dollar,USD,840,2,
|
||||
EGYPT,Egyptian Pound,EGP,818,2,
|
||||
EL SALVADOR,El Salvador Colon,SVC,222,2,
|
||||
EL SALVADOR,US Dollar,USD,840,2,
|
||||
EQUATORIAL GUINEA,CFA Franc BEAC,XAF,950,0,
|
||||
ERITREA,Nakfa,ERN,232,2,
|
||||
ESTONIA,Euro,EUR,978,2,
|
||||
ESWATINI,Lilangeni,SZL,748,2,
|
||||
ETHIOPIA,Ethiopian Birr,ETB,230,2,
|
||||
EUROPEAN UNION,Euro,EUR,978,2,
|
||||
"FALKLAND ISLANDS (THE) [MALVINAS]",Falkland Islands Pound,FKP,238,2,
|
||||
FAROE ISLANDS (THE),Danish Krone,DKK,208,2,
|
||||
FIJI,Fiji Dollar,FJD,242,2,
|
||||
FINLAND,Euro,EUR,978,2,
|
||||
FRANCE,Euro,EUR,978,2,
|
||||
FRENCH GUIANA,Euro,EUR,978,2,
|
||||
FRENCH POLYNESIA,CFP Franc,XPF,953,0,
|
||||
FRENCH SOUTHERN TERRITORIES (THE),Euro,EUR,978,2,
|
||||
GABON,CFA Franc BEAC,XAF,950,0,
|
||||
GAMBIA (THE),Dalasi,GMD,270,2,
|
||||
GEORGIA,Lari,GEL,981,2,
|
||||
GERMANY,Euro,EUR,978,2,
|
||||
GHANA,Ghana Cedi,GHS,936,2,
|
||||
GIBRALTAR,Gibraltar Pound,GIP,292,2,
|
||||
GREECE,Euro,EUR,978,2,
|
||||
GREENLAND,Danish Krone,DKK,208,2,
|
||||
GRENADA,East Caribbean Dollar,XCD,951,2,
|
||||
GUADELOUPE,Euro,EUR,978,2,
|
||||
GUAM,US Dollar,USD,840,2,
|
||||
GUATEMALA,Quetzal,GTQ,320,2,
|
||||
GUERNSEY,Pound Sterling,GBP,826,2,
|
||||
GUINEA,Guinean Franc,GNF,324,0,
|
||||
GUINEA-BISSAU,CFA Franc BCEAO,XOF,952,0,
|
||||
GUYANA,Guyana Dollar,GYD,328,2,
|
||||
HAITI,Gourde,HTG,332,2,
|
||||
HAITI,US Dollar,USD,840,2,
|
||||
HEARD ISLAND AND McDONALD ISLANDS,Australian Dollar,AUD,036,2,
|
||||
HOLY SEE (THE),Euro,EUR,978,2,
|
||||
HONDURAS,Lempira,HNL,340,2,
|
||||
HONG KONG,Hong Kong Dollar,HKD,344,2,
|
||||
HUNGARY,Forint,HUF,348,2,
|
||||
ICELAND,Iceland Krona,ISK,352,0,
|
||||
INDIA,Indian Rupee,INR,356,2,
|
||||
INDONESIA,Rupiah,IDR,360,2,
|
||||
INTERNATIONAL MONETARY FUND (IMF),SDR (Special Drawing Right),XDR,960,-,
|
||||
IRAN (ISLAMIC REPUBLIC OF),Iranian Rial,IRR,364,2,
|
||||
IRAQ,Iraqi Dinar,IQD,368,3,
|
||||
IRELAND,Euro,EUR,978,2,
|
||||
ISLE OF MAN,Pound Sterling,GBP,826,2,
|
||||
ISRAEL,New Israeli Sheqel,ILS,376,2,
|
||||
ITALY,Euro,EUR,978,2,
|
||||
JAMAICA,Jamaican Dollar,JMD,388,2,
|
||||
JAPAN,Yen,JPY,392,0,
|
||||
JERSEY,Pound Sterling,GBP,826,2,
|
||||
JORDAN,Jordanian Dinar,JOD,400,3,
|
||||
KAZAKHSTAN,Tenge,KZT,398,2,
|
||||
KENYA,Kenyan Shilling,KES,404,2,
|
||||
KIRIBATI,Australian Dollar,AUD,036,2,
|
||||
KOREA (THE DEMOCRATIC PEOPLE'S REPUBLIC OF),North Korean Won,KPW,408,2,
|
||||
KOREA (THE REPUBLIC OF),Won,KRW,410,0,
|
||||
KUWAIT,Kuwaiti Dinar,KWD,414,3,
|
||||
KYRGYZSTAN,Som,KGS,417,2,
|
||||
LAO PEOPLE'S DEMOCRATIC REPUBLIC (THE),Lao Kip,LAK,418,2,
|
||||
LATVIA,Euro,EUR,978,2,
|
||||
LEBANON,Lebanese Pound,LBP,422,2,
|
||||
LESOTHO,Loti,LSL,426,2,
|
||||
LESOTHO,Rand,ZAR,710,2,
|
||||
LIBERIA,Liberian Dollar,LRD,430,2,
|
||||
LIBYA,Libyan Dinar,LYD,434,3,
|
||||
LIECHTENSTEIN,Swiss Franc,CHF,756,2,
|
||||
LITHUANIA,Euro,EUR,978,2,
|
||||
LUXEMBOURG,Euro,EUR,978,2,
|
||||
MACAO,Pataca,MOP,446,2,
|
||||
NORTH MACEDONIA,Denar,MKD,807,2,
|
||||
MADAGASCAR,Malagasy Ariary,MGA,969,2,
|
||||
MALAWI,Malawi Kwacha,MWK,454,2,
|
||||
MALAYSIA,Malaysian Ringgit,MYR,458,2,
|
||||
MALDIVES,Rufiyaa,MVR,462,2,
|
||||
MALI,CFA Franc BCEAO,XOF,952,0,
|
||||
MALTA,Euro,EUR,978,2,
|
||||
MARSHALL ISLANDS (THE),US Dollar,USD,840,2,
|
||||
MARTINIQUE,Euro,EUR,978,2,
|
||||
MAURITANIA,Ouguiya,MRU,929,2,
|
||||
MAURITIUS,Mauritius Rupee,MUR,480,2,
|
||||
MAYOTTE,Euro,EUR,978,2,
|
||||
MEMBER COUNTRIES OF THE AFRICAN DEVELOPMENT BANK GROUP,ADB Unit of Account,XUA,965,-,
|
||||
MEXICO,Mexican Peso,MXN,484,2,
|
||||
MEXICO,Mexican Unidad de Inversion (UDI),MXV,979,2,
|
||||
MICRONESIA (FEDERATED STATES OF),US Dollar,USD,840,2,
|
||||
MOLDOVA (THE REPUBLIC OF),Moldovan Leu,MDL,498,2,
|
||||
MONACO,Euro,EUR,978,2,
|
||||
MONGOLIA,Tugrik,MNT,496,2,
|
||||
MONTENEGRO,Euro,EUR,978,2,
|
||||
MONTSERRAT,East Caribbean Dollar,XCD,951,2,
|
||||
MOROCCO,Moroccan Dirham,MAD,504,2,
|
||||
MOZAMBIQUE,Mozambique Metical,MZN,943,2,
|
||||
MYANMAR,Kyat,MMK,104,2,
|
||||
NAMIBIA,Namibia Dollar,NAD,516,2,
|
||||
NAMIBIA,Rand,ZAR,710,2,
|
||||
NAURU,Australian Dollar,AUD,036,2,
|
||||
NEPAL,Nepalese Rupee,NPR,524,2,
|
||||
NETHERLANDS (THE),Euro,EUR,978,2,
|
||||
NEW CALEDONIA,CFP Franc,XPF,953,0,
|
||||
NEW ZEALAND,New Zealand Dollar,NZD,554,2,
|
||||
NICARAGUA,Cordoba Oro,NIO,558,2,
|
||||
NIGER (THE),CFA Franc BCEAO,XOF,952,0,
|
||||
NIGERIA,Naira,NGN,566,2,
|
||||
NIUE,New Zealand Dollar,NZD,554,2,
|
||||
NORFOLK ISLAND,Australian Dollar,AUD,036,2,
|
||||
NORTHERN MARIANA ISLANDS (THE),US Dollar,USD,840,2,
|
||||
NORWAY,Norwegian Krone,NOK,578,2,
|
||||
OMAN,Rial Omani,OMR,512,3,
|
||||
PAKISTAN,Pakistan Rupee,PKR,586,2,
|
||||
PALAU,US Dollar,USD,840,2,
|
||||
"PALESTINE, STATE OF",No universal currency,,,,
|
||||
PANAMA,Balboa,PAB,590,2,
|
||||
PANAMA,US Dollar,USD,840,2,
|
||||
PAPUA NEW GUINEA,Kina,PGK,598,2,
|
||||
PARAGUAY,Guarani,PYG,600,0,
|
||||
PERU,Sol,PEN,604,2,
|
||||
PHILIPPINES (THE),Philippine Peso,PHP,608,2,
|
||||
PITCAIRN,New Zealand Dollar,NZD,554,2,
|
||||
POLAND,Zloty,PLN,985,2,
|
||||
PORTUGAL,Euro,EUR,978,2,
|
||||
PUERTO RICO,US Dollar,USD,840,2,
|
||||
QATAR,Qatari Rial,QAR,634,2,
|
||||
RÉUNION,Euro,EUR,978,2,
|
||||
ROMANIA,Romanian Leu,RON,946,2,
|
||||
RUSSIAN FEDERATION (THE),Russian Ruble,RUB,643,2,
|
||||
RWANDA,Rwanda Franc,RWF,646,0,
|
||||
SAINT BARTHÉLEMY,Euro,EUR,978,2,
|
||||
"SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA",Saint Helena Pound,SHP,654,2,
|
||||
SAINT KITTS AND NEVIS,East Caribbean Dollar,XCD,951,2,
|
||||
SAINT LUCIA,East Caribbean Dollar,XCD,951,2,
|
||||
SAINT MARTIN (FRENCH PART),Euro,EUR,978,2,
|
||||
SAINT PIERRE AND MIQUELON,Euro,EUR,978,2,
|
||||
SAINT VINCENT AND THE GRENADINES,East Caribbean Dollar,XCD,951,2,
|
||||
SAMOA,Tala,WST,882,2,
|
||||
SAN MARINO,Euro,EUR,978,2,
|
||||
SAO TOME AND PRINCIPE,Dobra,STN,930,2,
|
||||
SAUDI ARABIA,Saudi Riyal,SAR,682,2,
|
||||
SENEGAL,CFA Franc BCEAO,XOF,952,0,
|
||||
SERBIA,Serbian Dinar,RSD,941,2,
|
||||
SEYCHELLES,Seychelles Rupee,SCR,690,2,
|
||||
SIERRA LEONE,Leone,SLL,694,2,
|
||||
SINGAPORE,Singapore Dollar,SGD,702,2,
|
||||
SINT MAARTEN (DUTCH PART),Netherlands Antillean Guilder,ANG,532,2,
|
||||
"SISTEMA UNITARIO DE COMPENSACION REGIONAL DE PAGOS ""SUCRE""",Sucre,XSU,994,-,
|
||||
SLOVAKIA,Euro,EUR,978,2,
|
||||
SLOVENIA,Euro,EUR,978,2,
|
||||
SOLOMON ISLANDS,Solomon Islands Dollar,SBD,090,2,
|
||||
SOMALIA,Somali Shilling,SOS,706,2,
|
||||
SOUTH AFRICA,Rand,ZAR,710,2,
|
||||
SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS,No universal currency,,,,
|
||||
SOUTH SUDAN,South Sudanese Pound,SSP,728,2,
|
||||
SPAIN,Euro,EUR,978,2,
|
||||
SRI LANKA,Sri Lanka Rupee,LKR,144,2,
|
||||
SUDAN (THE),Sudanese Pound,SDG,938,2,
|
||||
SURINAME,Surinam Dollar,SRD,968,2,
|
||||
SVALBARD AND JAN MAYEN,Norwegian Krone,NOK,578,2,
|
||||
SWEDEN,Swedish Krona,SEK,752,2,
|
||||
SWITZERLAND,Swiss Franc,CHF,756,2,
|
||||
SWITZERLAND,WIR Euro,CHE,947,2,
|
||||
SWITZERLAND,WIR Franc,CHW,948,2,
|
||||
SYRIAN ARAB REPUBLIC,Syrian Pound,SYP,760,2,
|
||||
TAIWAN (PROVINCE OF CHINA),New Taiwan Dollar,TWD,901,2,
|
||||
TAJIKISTAN,Somoni,TJS,972,2,
|
||||
"TANZANIA, UNITED REPUBLIC OF",Tanzanian Shilling,TZS,834,2,
|
||||
THAILAND,Baht,THB,764,2,
|
||||
TIMOR-LESTE,US Dollar,USD,840,2,
|
||||
TOGO,CFA Franc BCEAO,XOF,952,0,
|
||||
TOKELAU,New Zealand Dollar,NZD,554,2,
|
||||
TONGA,Pa'anga,TOP,776,2,
|
||||
TRINIDAD AND TOBAGO,Trinidad and Tobago Dollar,TTD,780,2,
|
||||
TUNISIA,Tunisian Dinar,TND,788,3,
|
||||
TURKEY,Turkish Lira,TRY,949,2,
|
||||
TURKMENISTAN,Turkmenistan New Manat,TMT,934,2,
|
||||
TURKS AND CAICOS ISLANDS (THE),US Dollar,USD,840,2,
|
||||
TUVALU,Australian Dollar,AUD,036,2,
|
||||
UGANDA,Uganda Shilling,UGX,800,0,
|
||||
UKRAINE,Hryvnia,UAH,980,2,
|
||||
UNITED ARAB EMIRATES (THE),UAE Dirham,AED,784,2,
|
||||
UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE),Pound Sterling,GBP,826,2,
|
||||
UNITED STATES MINOR OUTLYING ISLANDS (THE),US Dollar,USD,840,2,
|
||||
UNITED STATES OF AMERICA (THE),US Dollar,USD,840,2,
|
||||
UNITED STATES OF AMERICA (THE),US Dollar (Next day),USN,997,2,
|
||||
URUGUAY,Peso Uruguayo,UYU,858,2,
|
||||
URUGUAY,Uruguay Peso en Unidades Indexadas (UI),UYI,940,0,
|
||||
URUGUAY,Unidad Previsional,UYW,927,4,
|
||||
UZBEKISTAN,Uzbekistan Sum,UZS,860,2,
|
||||
VANUATU,Vatu,VUV,548,0,
|
||||
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolívar Soberano,VES,928,2,
|
||||
VIET NAM,Dong,VND,704,0,
|
||||
VIRGIN ISLANDS (BRITISH),US Dollar,USD,840,2,
|
||||
VIRGIN ISLANDS (U.S.),US Dollar,USD,840,2,
|
||||
WALLIS AND FUTUNA,CFP Franc,XPF,953,0,
|
||||
WESTERN SAHARA,Moroccan Dirham,MAD,504,2,
|
||||
YEMEN,Yemeni Rial,YER,886,2,
|
||||
ZAMBIA,Zambian Kwacha,ZMW,967,2,
|
||||
ZIMBABWE,Zimbabwe Dollar,ZWL,932,2,
|
||||
ZZ01_Bond Markets Unit European_EURCO,Bond Markets Unit European Composite Unit (EURCO),XBA,955,-,
|
||||
ZZ02_Bond Markets Unit European_EMU-6,Bond Markets Unit European Monetary Unit (E.M.U.-6),XBB,956,-,
|
||||
ZZ03_Bond Markets Unit European_EUA-9,Bond Markets Unit European Unit of Account 9 (E.U.A.-9),XBC,957,-,
|
||||
ZZ04_Bond Markets Unit European_EUA-17,Bond Markets Unit European Unit of Account 17 (E.U.A.-17),XBD,958,-,
|
||||
ZZ06_Testing_Code,Codes specifically reserved for testing purposes,XTS,963,-,
|
||||
ZZ07_No_Currency,The codes assigned for transactions where no currency is involved,XXX,999,-,
|
||||
ZZ08_Gold,Gold,XAU,959,-,
|
||||
ZZ09_Palladium,Palladium,XPD,964,-,
|
||||
ZZ10_Platinum,Platinum,XPT,962,-,
|
||||
ZZ11_Silver,Silver,XAG,961,-,
|
||||
AFGHANISTAN,Afghani,AFA,004,,2003-01
|
||||
ÅLAND ISLANDS,Markka,FIM,246,,2002-03
|
||||
ALBANIA,Old Lek,ALK,008,,1989-12
|
||||
ANDORRA,Andorran Peseta,ADP,020,,2003-07
|
||||
ANDORRA,Spanish Peseta,ESP,724,,2002-03
|
||||
ANDORRA,French Franc,FRF,250,,2002-03
|
||||
ANGOLA,Kwanza,AOK,024,,1991-03
|
||||
ANGOLA,New Kwanza,AON,024,,2000-02
|
||||
ANGOLA,Kwanza Reajustado,AOR,982,,2000-02
|
||||
ARGENTINA,Austral,ARA,032,,1992-01
|
||||
ARGENTINA,Peso Argentino,ARP,032,,1985-07
|
||||
ARGENTINA,Peso,ARY,032,,1989 to 1990
|
||||
ARMENIA,Russian Ruble,RUR,810,,1994-08
|
||||
AUSTRIA,Schilling,ATS,040,,2002-03
|
||||
AZERBAIJAN,Azerbaijan Manat,AYM,945,,2005-10
|
||||
AZERBAIJAN,Azerbaijanian Manat,AZM,031,,2005-12
|
||||
AZERBAIJAN,Russian Ruble,RUR,810,,1994-08
|
||||
BELARUS,Belarusian Ruble,BYB,112,,2001-01
|
||||
BELARUS,Belarusian Ruble,BYR,974,,2017-01
|
||||
BELARUS,Russian Ruble,RUR,810,,1994-06
|
||||
BELGIUM,Convertible Franc,BEC,993,,1990-03
|
||||
BELGIUM,Belgian Franc,BEF,056,,2002-03
|
||||
BELGIUM,Financial Franc,BEL,992,,1990-03
|
||||
BOLIVIA,Peso boliviano,BOP,068,,1987-02
|
||||
BOSNIA AND HERZEGOVINA,Dinar,BAD,070,,1998-07
|
||||
BRAZIL,Cruzeiro,BRB,076,,1986-03
|
||||
BRAZIL,Cruzado,BRC,076,,1989-02
|
||||
BRAZIL,Cruzeiro,BRE,076,,1993-03
|
||||
BRAZIL,New Cruzado,BRN,076,,1990-03
|
||||
BRAZIL,Cruzeiro Real,BRR,987,,1994-07
|
||||
BULGARIA,Lev A/52,BGJ,100,,1989 to 1990
|
||||
BULGARIA,Lev A/62,BGK,100,,1989 to 1990
|
||||
BULGARIA,Lev,BGL,100,,2003-11
|
||||
BURMA,Kyat,BUK,104,,1990-02
|
||||
CROATIA,Croatian Dinar,HRD,191,,1995-01
|
||||
CROATIA,Croatian Kuna,HRK,191,,2015-06
|
||||
CYPRUS,Cyprus Pound,CYP,196,,2008-01
|
||||
CZECHOSLOVAKIA,Krona A/53,CSJ,203,,1989 to 1990
|
||||
CZECHOSLOVAKIA,Koruna,CSK,200,,1993-03
|
||||
ECUADOR,Sucre,ECS,218,,2000-09
|
||||
ECUADOR,Unidad de Valor Constante (UVC),ECV,983,,2000-09
|
||||
EQUATORIAL GUINEA,Ekwele,GQE,226,,1986-06
|
||||
ESTONIA,Kroon,EEK,233,,2011-01
|
||||
EUROPEAN MONETARY CO-OPERATION FUND (EMCF),European Currency Unit (E.C.U),XEU,954,,1999-01
|
||||
FINLAND,Markka,FIM,246,,2002-03
|
||||
FRANCE,French Franc,FRF,250,,2002-03
|
||||
FRENCH GUIANA,French Franc,FRF,250,,2002-03
|
||||
FRENCH SOUTHERN TERRITORIES,French Franc,FRF,250,,2002-03
|
||||
GEORGIA,Georgian Coupon,GEK,268,,1995-10
|
||||
GEORGIA,Russian Ruble,RUR,810,,1994-04
|
||||
GERMAN DEMOCRATIC REPUBLIC,Mark der DDR,DDM,278,,1990-07 to 1990-09
|
||||
GERMANY,Deutsche Mark,DEM,276,,2002-03
|
||||
GHANA,Cedi,GHC,288,,2008-01
|
||||
GHANA,Ghana Cedi,GHP,939,,2007-06
|
||||
GREECE,Drachma,GRD,300,,2002-03
|
||||
GUADELOUPE,French Franc,FRF,250,,2002-03
|
||||
GUINEA,Syli,GNE,324,,1989-12
|
||||
GUINEA,Syli,GNS,324,,1986-02
|
||||
GUINEA-BISSAU,Guinea Escudo,GWE,624,,1978 to 1981
|
||||
GUINEA-BISSAU,Guinea-Bissau Peso,GWP,624,,1997-05
|
||||
HOLY SEE (VATICAN CITY STATE),Italian Lira,ITL,380,,2002-03
|
||||
ICELAND,Old Krona,ISJ,352,,1989 to 1990
|
||||
IRELAND,Irish Pound,IEP,372,,2002-03
|
||||
ISRAEL,Pound,ILP,376,,1978 to 1981
|
||||
ISRAEL,Old Shekel,ILR,376,,1989 to 1990
|
||||
ITALY,Italian Lira,ITL,380,,2002-03
|
||||
KAZAKHSTAN,Russian Ruble,RUR,810,,1994-05
|
||||
KYRGYZSTAN,Russian Ruble,RUR,810,,1993-01
|
||||
LAO,Pathet Lao Kip,LAJ,418,,1979-12
|
||||
LATVIA,Latvian Lats,LVL,428,,2014-01
|
||||
LATVIA,Latvian Ruble,LVR,428,,1994-12
|
||||
LESOTHO,Loti,LSM,426,,1985-05
|
||||
LESOTHO,Financial Rand,ZAL,991,,1995-03
|
||||
LITHUANIA,Lithuanian Litas,LTL,440,,2014-12
|
||||
LITHUANIA,Talonas,LTT,440,,1993-07
|
||||
LUXEMBOURG,Luxembourg Convertible Franc,LUC,989,,1990-03
|
||||
LUXEMBOURG,Luxembourg Franc,LUF,442,,2002-03
|
||||
LUXEMBOURG,Luxembourg Financial Franc,LUL,988,,1990-03
|
||||
MADAGASCAR,Malagasy Franc,MGF,450,,2004-12
|
||||
MALAWI,Kwacha,MWK,454,,2016-02
|
||||
MALDIVES,Maldive Rupee,MVQ,462,,1989-12
|
||||
MALI,Mali Franc,MLF,466,,1984-11
|
||||
MALTA,Maltese Lira,MTL,470,,2008-01
|
||||
MALTA,Maltese Pound,MTP,470,,1983-06
|
||||
MARTINIQUE,French Franc,FRF,250,,2002-03
|
||||
MAURITANIA,Ouguiya,MRO,478,,2017-12
|
||||
MAYOTTE,French Franc,FRF,250,,2002-03
|
||||
MEXICO,Mexican Peso,MXP,484,,1993-01
|
||||
"MOLDOVA, REPUBLIC OF",Russian Ruble,RUR,810,,1993-12
|
||||
MONACO,French Franc,FRF,250,,2002-03
|
||||
MOZAMBIQUE,Mozambique Escudo,MZE,508,,1978 to 1981
|
||||
MOZAMBIQUE,Mozambique Metical,MZM,508,,2006-06
|
||||
NETHERLANDS,Netherlands Guilder,NLG,528,,2002-03
|
||||
NETHERLANDS ANTILLES,Netherlands Antillean Guilder,ANG,532,,2010-10
|
||||
NICARAGUA,Cordoba,NIC,558,,1990-10
|
||||
PERU,Sol,PEH,604,,1989 to 1990
|
||||
PERU,Inti,PEI,604,,1991-07
|
||||
PERU,Nuevo Sol ,PEN,604,,2015-12
|
||||
PERU,Sol,PES,604,,1986-02
|
||||
POLAND,Zloty,PLZ,616,,1997-01
|
||||
PORTUGAL,Portuguese Escudo,PTE,620,,2002-03
|
||||
RÉUNION,French Franc,FRF,250,,2002-03
|
||||
ROMANIA,Leu A/52,ROK,642,,1989 to 1990
|
||||
ROMANIA,Old Leu,ROL,642,,2005-06
|
||||
ROMANIA,New Romanian Leu ,RON,946,,2015-06
|
||||
RUSSIAN FEDERATION,Russian Ruble,RUR,810,,2004-01
|
||||
SAINT MARTIN,French Franc,FRF,250,,1999-01
|
||||
SAINT PIERRE AND MIQUELON,French Franc,FRF,250,,2002-03
|
||||
SAINT-BARTHÉLEMY,French Franc,FRF,250,,1999-01
|
||||
SAN MARINO,Italian Lira,ITL,380,,2002-03
|
||||
SAO TOME AND PRINCIPE,Dobra,STD,678,,2017-12
|
||||
SERBIA AND MONTENEGRO,Serbian Dinar,CSD,891,,2006-10
|
||||
SERBIA AND MONTENEGRO,Euro,EUR,978,,2006-10
|
||||
SLOVAKIA,Slovak Koruna,SKK,703,,2009-01
|
||||
SLOVENIA,Tolar,SIT,705,,2007-01
|
||||
SOUTH AFRICA,Financial Rand,ZAL,991,,1995-03
|
||||
SOUTH SUDAN,Sudanese Pound,SDG,938,,2012-09
|
||||
SOUTHERN RHODESIA,Rhodesian Dollar,RHD,716,,1978 to 1981
|
||||
SPAIN,Spanish Peseta,ESA,996,,1978 to 1981
|
||||
SPAIN,"""A"" Account (convertible Peseta Account)",ESB,995,,1994-12
|
||||
SPAIN,Spanish Peseta,ESP,724,,2002-03
|
||||
SUDAN,Sudanese Dinar,SDD,736,,2007-07
|
||||
SUDAN,Sudanese Pound,SDP,736,,1998-06
|
||||
SURINAME,Surinam Guilder,SRG,740,,2003-12
|
||||
SWAZILAND,Lilangeni,SZL,748,,2018-08
|
||||
SWITZERLAND,WIR Franc (for electronic),CHC,948,,2004-11
|
||||
TAJIKISTAN,Russian Ruble,RUR,810,,1995-05
|
||||
TAJIKISTAN,Tajik Ruble,TJR,762,,2001-04
|
||||
TIMOR-LESTE,Rupiah,IDR,360,,2002-07
|
||||
TIMOR-LESTE,Timor Escudo,TPE,626,,2002-11
|
||||
TURKEY,Old Turkish Lira,TRL,792,,2005-12
|
||||
TURKEY,New Turkish Lira,TRY,949,,2009-01
|
||||
TURKMENISTAN,Russian Ruble,RUR,810,,1993-10
|
||||
TURKMENISTAN,Turkmenistan Manat,TMM,795,,2009-01
|
||||
UGANDA,Uganda Shilling,UGS,800,,1987-05
|
||||
UGANDA,Old Shilling,UGW,800,,1989 to 1990
|
||||
UKRAINE,Karbovanet,UAK,804,,1996-09
|
||||
UNION OF SOVIET SOCIALIST REPUBLICS,Rouble,SUR,810,,1990-12
|
||||
UNITED STATES,US Dollar (Same day),USS,998,,2014-03
|
||||
URUGUAY,Old Uruguay Peso,UYN,858,,1989-12
|
||||
URUGUAY,Uruguayan Peso,UYP,858,,1993-03
|
||||
UZBEKISTAN,Russian Ruble,RUR,810,,1994-07
|
||||
VENEZUELA,Bolivar,VEB,862,,2008-01
|
||||
VENEZUELA,Bolivar Fuerte,VEF,937,,2011-12
|
||||
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolivar,VEF,937,,2016-02
|
||||
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolívar,VEF,937,,2018-08
|
||||
VIETNAM,Old Dong,VNC,704,,1989-1990
|
||||
"YEMEN, DEMOCRATIC",Yemeni Dinar,YDD,720,,1991-09
|
||||
YUGOSLAVIA,New Yugoslavian Dinar,YUD,890,,1990-01
|
||||
YUGOSLAVIA,New Dinar,YUM,891,,2003-07
|
||||
YUGOSLAVIA,Yugoslavian Dinar,YUN,890,,1995-11
|
||||
ZAIRE,New Zaire,ZRN,180,,1999-06
|
||||
ZAIRE,Zaire,ZRZ,180,,1994-02
|
||||
ZAMBIA,Zambian Kwacha,ZMK,894,,2012-12
|
||||
ZIMBABWE,Rhodesian Dollar,ZWC,716,,1989-12
|
||||
ZIMBABWE,Zimbabwe Dollar (old),ZWD,716,,2006-08
|
||||
ZIMBABWE,Zimbabwe Dollar,ZWD,716,,2008-08
|
||||
ZIMBABWE,Zimbabwe Dollar (new),ZWN,942,,2006-09
|
||||
ZIMBABWE,Zimbabwe Dollar,ZWR,935,,2009-06
|
||||
ZZ01_Gold-Franc,Gold-Franc,XFO,,,2006-10
|
||||
ZZ02_RINET Funds Code,RINET Funds Code,XRE,,,1999-11
|
||||
ZZ05_UIC-Franc,UIC-Franc,XFU,,,2013-11
|
|
|
@ -4,9 +4,20 @@
|
|||
],
|
||||
"copyright": "Copyright © 2024, Andrew Lalis",
|
||||
"dependencies": {
|
||||
"handy-httpd": "~>8.4.0"
|
||||
"asdf": "~>0.7.17",
|
||||
"botan": "~>1.13.6",
|
||||
"d2sqlite3": "~>1.0.0",
|
||||
"handy-httpd": "~>8.4.0",
|
||||
"jwt": "~>0.4.0",
|
||||
"slf4d": "~>3.0.1"
|
||||
},
|
||||
"description": "Backend API for Finnow.",
|
||||
"license": "proprietary",
|
||||
"name": "finnow-api"
|
||||
"name": "finnow-api",
|
||||
"stringImportPaths": [
|
||||
"."
|
||||
],
|
||||
"subConfigurations": {
|
||||
"d2sqlite3": "all-included"
|
||||
}
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"asdf": "0.7.17",
|
||||
"botan": "1.13.6",
|
||||
"botan-math": "1.0.4",
|
||||
"d2sqlite3": "1.0.0",
|
||||
"handy-httpd": "8.4.0",
|
||||
"httparsed": "1.2.1",
|
||||
"jwt": "0.4.0",
|
||||
"memutils": "1.0.10",
|
||||
"mir-algorithm": "3.22.1",
|
||||
"mir-core": "1.7.1",
|
||||
"path-matcher": "1.2.0",
|
||||
"silly": "1.1.1",
|
||||
"slf4d": "3.0.1",
|
||||
"streams": "3.5.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
-- This schema is included at compile-time into data : SqliteDataSource.
|
||||
|
||||
-- Basic/Utility Entities
|
||||
|
||||
CREATE TABLE profile_property (
|
||||
property TEXT PRIMARY KEY,
|
||||
value TEXT DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE attachment (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uploaded_at TEXT NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
content_type TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
content BLOB NOT NULL
|
||||
);
|
||||
|
||||
-- Account Entities
|
||||
|
||||
CREATE TABLE account (
|
||||
id INTEGER PRIMARY KEY,
|
||||
created_at TEXT NOT NULL,
|
||||
archived BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
type TEXT NOT NULL,
|
||||
number_suffix TEXT,
|
||||
name TEXT NOT NULL,
|
||||
currency TEXT NOT NULL,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE account_credit_card_properties (
|
||||
account_id INTEGER PRIMARY KEY,
|
||||
credit_limit TEXT,
|
||||
CONSTRAINT fk_account_credit_card_properties_account
|
||||
FOREIGN KEY (account_id) REFERENCES account(id)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Transaction Entities
|
||||
|
||||
CREATE TABLE transaction_vendor (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE transaction_category (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_id INTEGER,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
color TEXT NOT NULL DEFAULT 'FFFFFF',
|
||||
CONSTRAINT fk_transaction_category_parent
|
||||
FOREIGN KEY (parent_id) REFERENCES transaction_category(id)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE transaction_tag (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE "transaction" (
|
||||
id INTEGER PRIMARY KEY,
|
||||
timestamp TEXT NOT NULL,
|
||||
added_at TEXT NOT NULL,
|
||||
amount TEXT NOT NULL,
|
||||
currency TEXT NOT NULL,
|
||||
description TEXT,
|
||||
vendor_id INTEGER,
|
||||
category_id INTEGER,
|
||||
CONSTRAINT fk_transaction_vendor
|
||||
FOREIGN KEY (vendor_id) REFERENCES transaction_vendor(id)
|
||||
ON UPDATE CASCADE ON DELETE SET NULL,
|
||||
CONSTRAINT fk_transaction_category
|
||||
FOREIGN KEY (category_id) REFERENCES transaction_category(id)
|
||||
ON UPDATE CASCADE ON DELETE SET NULL
|
||||
);
|
||||
|
||||
|
|
@ -1,17 +1,50 @@
|
|||
import slf4d;
|
||||
import handy_httpd;
|
||||
import handy_httpd.handlers.path_handler;
|
||||
|
||||
import model.base;
|
||||
import handy_httpd.handlers.filtered_handler;
|
||||
|
||||
void main() {
|
||||
ServerConfig cfg;
|
||||
cfg.workerPoolSize = 5;
|
||||
cfg.port = 8080;
|
||||
PathHandler pathHandler = new PathHandler();
|
||||
pathHandler.addMapping(Method.GET, "/status", (ref ctx) {
|
||||
ctx.response.writeBodyString("online");
|
||||
});
|
||||
HttpServer server = new HttpServer(pathHandler, cfg);
|
||||
HttpServer server = new HttpServer(buildHandlers(), cfg);
|
||||
server.start();
|
||||
}
|
||||
|
||||
PathHandler buildHandlers() {
|
||||
import profile;
|
||||
|
||||
const API_PATH = "/api";
|
||||
PathHandler pathHandler = new PathHandler();
|
||||
|
||||
// Generic, public endpoints:
|
||||
pathHandler.addMapping(Method.GET, API_PATH ~ "/status", (ref ctx) {
|
||||
ctx.response.writeBodyString("online");
|
||||
});
|
||||
pathHandler.addMapping(Method.OPTIONS, API_PATH ~ "/**", (ref ctx) {});
|
||||
|
||||
// Auth Entrypoints:
|
||||
import auth.api;
|
||||
import auth.service;
|
||||
pathHandler.addMapping(Method.POST, API_PATH ~ "/login", &postLogin);
|
||||
pathHandler.addMapping(Method.POST, API_PATH ~ "/register", &postRegister);
|
||||
|
||||
// Authenticated endpoints:
|
||||
PathHandler a = new PathHandler();
|
||||
a.addMapping(Method.GET, API_PATH ~ "/me", &getMyUser);
|
||||
a.addMapping(Method.GET, API_PATH ~ "/profiles", &handleGetProfiles);
|
||||
a.addMapping(Method.POST, API_PATH ~ "/profiles", &handleCreateNewProfile);
|
||||
a.addMapping(Method.DELETE, API_PATH ~ "/profiles/:name", &handleDeleteProfile);
|
||||
a.addMapping(Method.GET, API_PATH ~ "/profiles/:profile/properties", &handleGetProperties);
|
||||
a.addMapping(Method.GET, API_PATH ~ "/profiles/:profile/accounts", (ref ctx) {
|
||||
ctx.response.writeBodyString("your accounts!");
|
||||
});
|
||||
|
||||
HttpRequestFilter tokenAuthenticationFilter = new TokenAuthenticationFilter(SECRET);
|
||||
pathHandler.addMapping(API_PATH ~ "/**", new FilteredRequestHandler(
|
||||
a,
|
||||
[tokenAuthenticationFilter]
|
||||
));
|
||||
|
||||
return pathHandler;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/// API endpoints for authentication-related functions, like registration and login.
|
||||
module auth.api;
|
||||
|
||||
import handy_httpd;
|
||||
import handy_httpd.components.optional;
|
||||
import slf4d;
|
||||
|
||||
import auth.model;
|
||||
import auth.dao;
|
||||
import auth.dto;
|
||||
import auth.service;
|
||||
|
||||
void postLogin(ref HttpRequestContext ctx) {
|
||||
LoginCredentials loginCredentials;
|
||||
try {
|
||||
loginCredentials = LoginCredentials.parse(ctx.request.readBodyAsJson());
|
||||
} catch (Exception e) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
}
|
||||
if (!validateUsername(loginCredentials.username)) {
|
||||
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
||||
return;
|
||||
}
|
||||
UserRepository userRepo = new FileSystemUserRepository();
|
||||
Optional!User optionalUser = userRepo.findByUsername(loginCredentials.username);
|
||||
if (optionalUser.isNull) {
|
||||
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
||||
return;
|
||||
}
|
||||
import botan.passhash.bcrypt : checkBcrypt;
|
||||
if (!checkBcrypt(loginCredentials.password, optionalUser.value.passwordHash)) {
|
||||
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
||||
return;
|
||||
}
|
||||
string token = generateAccessToken(optionalUser.value);
|
||||
ctx.response.status = HttpStatus.OK;
|
||||
ctx.response.writeBodyString(TokenResponse(token).toJson(), "application/json");
|
||||
}
|
||||
|
||||
void postRegister(ref HttpRequestContext ctx) {
|
||||
RegistrationData registrationData;
|
||||
try {
|
||||
registrationData = RegistrationData.parse(ctx.request.readBodyAsJson());
|
||||
} catch (Exception e) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
return;
|
||||
}
|
||||
if (!validateUsername(registrationData.username)) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
ctx.response.writeBodyString("Invalid username.");
|
||||
return;
|
||||
}
|
||||
if (!validatePassword(registrationData.password)) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
ctx.response.writeBodyString("Invalid password.");
|
||||
return;
|
||||
}
|
||||
UserRepository userRepo = new FileSystemUserRepository();
|
||||
if (!userRepo.findByUsername(registrationData.username).isNull) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
ctx.response.writeBodyString("Username is taken.");
|
||||
return;
|
||||
}
|
||||
|
||||
import botan.passhash.bcrypt : generateBcrypt;
|
||||
import botan.rng.auto_rng;
|
||||
RandomNumberGenerator rng = new AutoSeededRNG();
|
||||
string passwordHash = generateBcrypt(registrationData.password, rng, 12);
|
||||
userRepo.createUser(registrationData.username, passwordHash);
|
||||
infoF!"Created user: %s"(registrationData.username);
|
||||
}
|
||||
|
||||
void getMyUser(ref HttpRequestContext ctx) {
|
||||
AuthContext auth = getAuthContext(ctx);
|
||||
ctx.response.writeBodyString(auth.user.username);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
module auth.dao;
|
||||
|
||||
import handy_httpd.components.optional;
|
||||
import auth.model;
|
||||
|
||||
interface UserRepository {
|
||||
Optional!User findByUsername(string username);
|
||||
User createUser(string username, string passwordHash);
|
||||
void deleteByUsername(string username);
|
||||
}
|
||||
|
||||
/**
|
||||
* User implementation that stores each user's data in a separate directory.
|
||||
*/
|
||||
class FileSystemUserRepository : UserRepository {
|
||||
import std.path;
|
||||
import std.file;
|
||||
import std.json;
|
||||
|
||||
private string usersDir;
|
||||
|
||||
this(string usersDir = "users") {
|
||||
this.usersDir = usersDir;
|
||||
}
|
||||
|
||||
Optional!User findByUsername(string username) {
|
||||
if (
|
||||
!validateUsername(username) ||
|
||||
!exists(getUserDir(username)) ||
|
||||
!exists(getUserDataFile(username))
|
||||
) {
|
||||
return Optional!User.empty;
|
||||
}
|
||||
JSONValue userObj = parseJSON(readText(getUserDataFile(username)));
|
||||
return Optional!User.of(User(
|
||||
username,
|
||||
userObj.object["passwordHash"].str
|
||||
));
|
||||
}
|
||||
|
||||
User createUser(string username, string passwordHash) {
|
||||
if (!validateUsername(username)) throw new Exception("Invalid username");
|
||||
if (exists(getUserDir(username))) throw new Exception("User already exists.");
|
||||
JSONValue userObj = JSONValue.emptyObject;
|
||||
userObj.object["passwordHash"] = JSONValue(passwordHash);
|
||||
string jsonStr = userObj.toPrettyString();
|
||||
if (!exists(this.usersDir)) mkdir(this.usersDir);
|
||||
mkdir(getUserDir(username));
|
||||
std.file.write(getUserDataFile(username), cast(ubyte[]) jsonStr);
|
||||
return User(username, passwordHash);
|
||||
}
|
||||
|
||||
void deleteByUsername(string username) {
|
||||
if (validateUsername(username) && exists(getUserDir(username))) {
|
||||
rmdirRecurse(getUserDir(username));
|
||||
}
|
||||
}
|
||||
|
||||
private string getUserDir(string username) {
|
||||
return buildPath(this.usersDir, username);
|
||||
}
|
||||
|
||||
private string getUserDataFile(string username) {
|
||||
return buildPath(this.usersDir, username, "user-data.json");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/// Defines data-transfer objects for the API's authentication mechanisms.
|
||||
module auth.dto;
|
||||
|
||||
import handy_httpd;
|
||||
import std.json;
|
||||
|
||||
struct LoginCredentials {
|
||||
string username;
|
||||
string password;
|
||||
|
||||
static LoginCredentials parse(JSONValue obj) {
|
||||
if (
|
||||
obj.type != JSONType.OBJECT ||
|
||||
"username" !in obj.object ||
|
||||
"password" !in obj.object ||
|
||||
obj.object["username"].type != JSONType.STRING ||
|
||||
obj.object["password"].type != JSONType.STRING
|
||||
) {
|
||||
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Malformed login credentials.");
|
||||
}
|
||||
return LoginCredentials(
|
||||
obj.object["username"].str,
|
||||
obj.object["password"].str
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct TokenResponse {
|
||||
string token;
|
||||
|
||||
string toJson() {
|
||||
JSONValue obj = JSONValue.emptyObject;
|
||||
obj.object["token"] = JSONValue(token);
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
struct RegistrationData {
|
||||
string username;
|
||||
string password;
|
||||
|
||||
static RegistrationData parse(JSONValue obj) {
|
||||
LoginCredentials lc = LoginCredentials.parse(obj);
|
||||
return RegistrationData(lc.username, lc.password);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Defines models for the Finnow API's authentication system.
|
||||
*/
|
||||
module auth.model;
|
||||
|
||||
/**
|
||||
* The user is the basic authenticated entity representing someone who has one
|
||||
* or more profiles.
|
||||
*/
|
||||
struct User {
|
||||
const string username;
|
||||
const string passwordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a username string.
|
||||
* Params:
|
||||
* username = The username to check.
|
||||
* Returns: True if the username is valid.
|
||||
*/
|
||||
bool validateUsername(string username) {
|
||||
import std.regex;
|
||||
import std.uni : toLower;
|
||||
if (username is null || username.length < 3) return false;
|
||||
const string[] RESERVED_USERNAMES = ["user", "admin"];
|
||||
static foreach (reservedUsername; RESERVED_USERNAMES) {
|
||||
if (toLower(username) == reservedUsername) return false;
|
||||
}
|
||||
auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_]+$`);
|
||||
return !matchFirst(username, r).empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a password is of sufficient complexity.
|
||||
* Params:
|
||||
* password = The password to check.
|
||||
* Returns: True if the password is sufficiently complex.
|
||||
*/
|
||||
bool validatePassword(string password) {
|
||||
return password !is null && password.length >= 8;
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
module auth.service;
|
||||
|
||||
import handy_httpd;
|
||||
import handy_httpd.components.optional;
|
||||
import slf4d;
|
||||
|
||||
import auth.model;
|
||||
import auth.dao;
|
||||
import handy_httpd.handlers.filtered_handler;
|
||||
|
||||
const SECRET = "temporary-insecure-secret"; // TODO: Load secret from application config!
|
||||
|
||||
/**
|
||||
* Generates a new JWT access token for a user.
|
||||
* Params:
|
||||
* user = The user to generate the token for.
|
||||
* Returns: The token.
|
||||
*/
|
||||
string generateAccessToken(User user) {
|
||||
import jwt.jwt : Token;
|
||||
import jwt.algorithms : JWTAlgorithm;
|
||||
import std.datetime;
|
||||
const TIMEOUT_MINUTES = 30;
|
||||
Token token = new Token(JWTAlgorithm.HS512);
|
||||
token.claims.aud("finnow-api");
|
||||
token.claims.sub(user.username);
|
||||
token.claims.exp(Clock.currTime().toUnixTime() + TIMEOUT_MINUTES * 60);
|
||||
token.claims.iss("finnow-api");
|
||||
return token.encode(SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* A request filter that only permits authenticated requests to be processed.
|
||||
*/
|
||||
class TokenAuthenticationFilter : HttpRequestFilter {
|
||||
private static const AUTH_METADATA_KEY = "AuthContext";
|
||||
private immutable string secret;
|
||||
|
||||
this(string secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
void apply(ref HttpRequestContext ctx, FilterChain fc) {
|
||||
Optional!AuthContext optionalAuth = validateAuthContext(ctx);
|
||||
if (!optionalAuth.isNull) {
|
||||
ctx.metadata[AUTH_METADATA_KEY] = optionalAuth.value;
|
||||
fc.doFilter(ctx); // Only continue the filter chain if a valid auth context was obtained.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the current request's authentication status.
|
||||
class AuthContext {
|
||||
string token;
|
||||
User user;
|
||||
this(string token, User user) {
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the authentication context from a request context
|
||||
* that was previously passed through this filter.
|
||||
* Params:
|
||||
* ctx = The request context to get.
|
||||
* Returns: The auth context that has been set.
|
||||
*/
|
||||
AuthContext getAuthContext(ref HttpRequestContext ctx) {
|
||||
return cast(AuthContext) ctx.metadata[TokenAuthenticationFilter.AUTH_METADATA_KEY];
|
||||
}
|
||||
|
||||
private Optional!AuthContext validateAuthContext(ref HttpRequestContext ctx) {
|
||||
import jwt.jwt : verify, Token;
|
||||
import jwt.algorithms : JWTAlgorithm;
|
||||
import std.typecons;
|
||||
|
||||
const HEADER_NAME = "Authorization";
|
||||
if (!ctx.request.headers.contains(HEADER_NAME)) {
|
||||
return setUnauthorized(ctx, "Missing Authorization header.");
|
||||
}
|
||||
|
||||
string authorizationHeader = ctx.request.headers.getFirst(HEADER_NAME).orElse("");
|
||||
if (authorizationHeader.length < 7 || authorizationHeader[0..7] != "Bearer ") {
|
||||
return setUnauthorized(ctx, "Invalid Authorization header format. Expected bearer token.");
|
||||
}
|
||||
|
||||
string rawToken = authorizationHeader[7..$];
|
||||
try {
|
||||
Token token = verify(rawToken, SECRET, [JWTAlgorithm.HS512]);
|
||||
string username = token.claims.sub;
|
||||
UserRepository userRepo = new FileSystemUserRepository();
|
||||
Optional!User optionalUser = userRepo.findByUsername(username);
|
||||
if (optionalUser.isNull) {
|
||||
return setUnauthorized(ctx, "User does not exist.");
|
||||
}
|
||||
return Optional!AuthContext.of(new AuthContext(rawToken, optionalUser.value));
|
||||
} catch (Exception e) {
|
||||
warn("Failed to verify user token.", e);
|
||||
return setUnauthorized(ctx, "Invalid or malformed token.");
|
||||
}
|
||||
}
|
||||
|
||||
private Optional!AuthContext setUnauthorized(ref HttpRequestContext ctx, string msg) {
|
||||
ctx.response.status = HttpStatus.UNAUTHORIZED;
|
||||
ctx.response.writeBodyString(msg);
|
||||
return Optional!AuthContext.empty;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
module data.account;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
module data.base;
|
||||
|
||||
import model;
|
||||
import handy_httpd.components.optional;
|
||||
|
||||
interface DataSource {
|
||||
PropertiesRepository getPropertiesRepository();
|
||||
}
|
||||
|
||||
interface PropertiesRepository {
|
||||
Optional!string findProperty(string propertyName);
|
||||
void setProperty(string name, string value);
|
||||
void deleteProperty(string name);
|
||||
ProfileProperty[] findAll();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module data;
|
||||
|
||||
public import data.base;
|
||||
|
||||
// Utility imports for items commonly used alongside data.
|
||||
public import handy_httpd.components.optional;
|
|
@ -0,0 +1,82 @@
|
|||
module data.sqlite;
|
||||
|
||||
import d2sqlite3;
|
||||
import slf4d;
|
||||
import data.base;
|
||||
import model;
|
||||
|
||||
class SqliteDataSource : DataSource {
|
||||
const SCHEMA = import("schema.sql");
|
||||
|
||||
private const string dbPath;
|
||||
private Database db;
|
||||
|
||||
this(string path) {
|
||||
this.dbPath = path;
|
||||
import std.file : exists;
|
||||
bool needsInit = !exists(path);
|
||||
this.db = Database(path);
|
||||
if (needsInit) {
|
||||
infoF!"Initializing database: %s"(dbPath);
|
||||
db.run(SCHEMA);
|
||||
}
|
||||
}
|
||||
|
||||
PropertiesRepository getPropertiesRepository() {
|
||||
return new SqlitePropertiesRepository(db);
|
||||
}
|
||||
}
|
||||
|
||||
class SqliteRepository {
|
||||
private Database db;
|
||||
this(Database db) {
|
||||
this.db = db;
|
||||
}
|
||||
}
|
||||
|
||||
class SqlitePropertiesRepository : SqliteRepository, PropertiesRepository {
|
||||
this(Database db) {
|
||||
super(db);
|
||||
}
|
||||
|
||||
Optional!string findProperty(string propertyName) {
|
||||
Statement stmt = this.db.prepare("SELECT value FROM profile_property WHERE property = ?");
|
||||
stmt.bind(1, propertyName);
|
||||
ResultRange result = stmt.execute();
|
||||
if (result.empty) return Optional!string.empty;
|
||||
return Optional!string.of(result.front.peek!string(0));
|
||||
}
|
||||
|
||||
void setProperty(string name, string value) {
|
||||
if (findProperty(name).isNull) {
|
||||
Statement stmt = this.db.prepare("INSERT INTO profile_property (property, value) VALUES (?, ?)");
|
||||
stmt.bind(1, name);
|
||||
stmt.bind(2, value);
|
||||
stmt.execute();
|
||||
} else {
|
||||
Statement stmt = this.db.prepare("UPDATE profile_property SET value = ? WHERE property = ?");
|
||||
stmt.bind(1, value);
|
||||
stmt.bind(2, name);
|
||||
stmt.execute();
|
||||
}
|
||||
}
|
||||
|
||||
void deleteProperty(string name) {
|
||||
Statement stmt = this.db.prepare("DELETE FROM profile_property WHERE property = ?");
|
||||
stmt.bind(1, name);
|
||||
stmt.execute();
|
||||
}
|
||||
|
||||
ProfileProperty[] findAll() {
|
||||
Statement stmt = this.db.prepare("SELECT * FROM profile_property ORDER BY property ASC");
|
||||
ResultRange result = stmt.execute();
|
||||
ProfileProperty[] props;
|
||||
foreach (Row row; result) {
|
||||
ProfileProperty prop;
|
||||
prop.property = row.peek!string("property");
|
||||
prop.value = row.peek!string("value");
|
||||
props ~= prop;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
module model.account;
|
||||
|
||||
import model.base;
|
||||
import std.datetime;
|
||||
|
||||
struct Account {
|
||||
ulong id;
|
||||
SysTime createdAt;
|
||||
bool archived;
|
||||
string type;
|
||||
string numberSuffix;
|
||||
string name;
|
||||
string currency;
|
||||
string description;
|
||||
}
|
||||
|
||||
struct AccountCreditCardProperties {
|
||||
ulong account_id;
|
||||
string creditLimit;
|
||||
}
|
|
@ -1,37 +1,17 @@
|
|||
module model.base;
|
||||
|
||||
/**
|
||||
* The base class for all persistent entities with a unique integer id.
|
||||
* It offers some basic utilities like equality and comparison by default.
|
||||
*/
|
||||
abstract class IdEntity {
|
||||
const ulong id;
|
||||
import std.datetime;
|
||||
|
||||
this(ulong id) {
|
||||
this.id = id;
|
||||
}
|
||||
struct ProfileProperty {
|
||||
string property;
|
||||
string value;
|
||||
}
|
||||
|
||||
override bool opEquals(Object other) const {
|
||||
if (IdEntity e = cast(IdEntity) other) {
|
||||
return e.id == id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override size_t toHash() const {
|
||||
import std.conv : to;
|
||||
return id.to!size_t;
|
||||
}
|
||||
|
||||
override string toString() const {
|
||||
import std.format : format;
|
||||
return format!"IdEntity(id = %d)"(id);
|
||||
}
|
||||
|
||||
override int opCmp(Object other) const {
|
||||
if (IdEntity e = cast(IdEntity) other) {
|
||||
return this.id < e.id;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
struct Attachment {
|
||||
ulong id;
|
||||
SysTime uploadedAt;
|
||||
string filename;
|
||||
string contentType;
|
||||
ulong size;
|
||||
ubyte[] content;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
module model.currency;
|
||||
|
||||
struct Currency {
|
||||
const char[3] code;
|
||||
const ubyte fractionalDigits;
|
||||
const ushort numericCode;
|
||||
|
||||
import std.traits : isSomeString, EnumMembers;
|
||||
static Currency ofCode(S)(S code) if (isSomeString!S) {
|
||||
if (code.length != 3) {
|
||||
throw new Exception("Invalid currency code: " ~ code);
|
||||
}
|
||||
static foreach (c; EnumMembers!Currencies) {
|
||||
if (c.code == code) return c;
|
||||
}
|
||||
throw new Exception("Unknown currency code: " ~ code);
|
||||
}
|
||||
}
|
||||
|
||||
enum Currencies : Currency {
|
||||
USD = Currency("USD", 2, 840),
|
||||
CAD = Currency("CAD", 2, 124),
|
||||
GBP = Currency("GBP", 2, 826),
|
||||
EUR = Currency("EUR", 2, 978),
|
||||
CHF = Currency("CHF", 2, 756),
|
||||
ZAR = Currency("ZAR", 2, 710),
|
||||
JPY = Currency("JPY", 0, 392),
|
||||
INR = Currency("INR", 2, 356)
|
||||
}
|
||||
|
||||
unittest {
|
||||
assert(Currency.ofCode("USD") == Currencies.USD);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module model;
|
||||
|
||||
public import model.base;
|
||||
public import model.account;
|
||||
public import model.transaction;
|
||||
|
||||
// Additional utility imports used often alongside models.
|
||||
public import handy_httpd.components.optional;
|
|
@ -0,0 +1,33 @@
|
|||
module model.transaction;
|
||||
|
||||
import std.datetime;
|
||||
import model.currency;
|
||||
|
||||
struct TransactionVendor {
|
||||
ulong id;
|
||||
string name;
|
||||
string description;
|
||||
}
|
||||
|
||||
struct TransactionCategory {
|
||||
ulong id;
|
||||
ulong parentId;
|
||||
string name;
|
||||
string color;
|
||||
}
|
||||
|
||||
struct TransactionTag {
|
||||
ulong id;
|
||||
string name;
|
||||
}
|
||||
|
||||
struct Transaction {
|
||||
ulong id;
|
||||
SysTime timestamp;
|
||||
SysTime addedAt;
|
||||
string amount;
|
||||
Currency currency;
|
||||
string description;
|
||||
ulong vendorId;
|
||||
ulong categoryId;
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* This module contains everything related to a user's Profiles, including
|
||||
* data repositories, API endpoints, models, and logic.
|
||||
*/
|
||||
module profile;
|
||||
|
||||
import handy_httpd;
|
||||
import handy_httpd.components.optional;
|
||||
import auth.model : User;
|
||||
import auth.service : AuthContext, getAuthContext;
|
||||
import data;
|
||||
import model;
|
||||
|
||||
import std.json;
|
||||
import asdf;
|
||||
|
||||
const DEFAULT_USERS_DIR = "users";
|
||||
|
||||
/**
|
||||
* A profile is a complete set of Finnow financial data, all stored in one
|
||||
* single database file. The profile's name is used to lookup the database
|
||||
* partition for its data. A user may own multiple profiles.
|
||||
*/
|
||||
class Profile {
|
||||
string name;
|
||||
|
||||
this(string name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
override int opCmp(Object other) const {
|
||||
if (Profile p = cast(Profile) other) {
|
||||
return this.name < p.name;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a profile name.
|
||||
* Params:
|
||||
* name = The name to check.
|
||||
* Returns: True if the profile name is valid.
|
||||
*/
|
||||
bool validateProfileName(string name) {
|
||||
import std.regex;
|
||||
import std.uni : toLower;
|
||||
if (name is null || name.length < 3) return false;
|
||||
auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_]+$`);
|
||||
return !matchFirst(name, r).empty;
|
||||
}
|
||||
|
||||
interface ProfileRepository {
|
||||
Optional!Profile findByName(string name);
|
||||
Profile createProfile(string name);
|
||||
Profile[] findAll();
|
||||
void deleteByName(string name);
|
||||
DataSource getDataSource(in Profile profile);
|
||||
}
|
||||
|
||||
class FileSystemProfileRepository : ProfileRepository {
|
||||
import std.path;
|
||||
import std.file;
|
||||
import data;
|
||||
import data.sqlite;
|
||||
|
||||
private const string usersDir;
|
||||
private const string username;
|
||||
|
||||
this(string usersDir, string username) {
|
||||
this.usersDir = usersDir;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
this(string username) {
|
||||
this(DEFAULT_USERS_DIR, username);
|
||||
}
|
||||
|
||||
Optional!Profile findByName(string name) {
|
||||
string path = getProfilePath(name);
|
||||
if (!exists(path)) return Optional!Profile.empty;
|
||||
return Optional!Profile.of(new Profile(name));
|
||||
}
|
||||
|
||||
Profile createProfile(string name) {
|
||||
string path = getProfilePath(name);
|
||||
if (exists(path)) throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Profile already exists.");
|
||||
if (!exists(getProfilesDir())) mkdir(getProfilesDir());
|
||||
DataSource ds = new SqliteDataSource(path);
|
||||
import std.datetime;
|
||||
auto propsRepo = ds.getPropertiesRepository();
|
||||
propsRepo.setProperty("name", name);
|
||||
propsRepo.setProperty("createdAt", Clock.currTime(UTC()).toISOExtString());
|
||||
propsRepo.setProperty("user", username);
|
||||
return new Profile(name);
|
||||
}
|
||||
|
||||
Profile[] findAll() {
|
||||
string profilesDir = getProfilesDir();
|
||||
if (!exists(profilesDir)) return [];
|
||||
Profile[] profiles;
|
||||
foreach (DirEntry entry; dirEntries(profilesDir, SpanMode.shallow, false)) {
|
||||
import std.string : endsWith;
|
||||
const suffix = ".sqlite";
|
||||
if (endsWith(entry.name, suffix)) {
|
||||
string profileName = baseName(entry.name, suffix);
|
||||
profiles ~= new Profile(profileName);
|
||||
}
|
||||
}
|
||||
import std.algorithm.sorting : sort;
|
||||
sort(profiles);
|
||||
return profiles;
|
||||
}
|
||||
|
||||
void deleteByName(string name) {
|
||||
string path = getProfilePath(name);
|
||||
if (exists(path)) {
|
||||
std.file.remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
DataSource getDataSource(in Profile profile) {
|
||||
return new SqliteDataSource(getProfilePath(profile.name));
|
||||
}
|
||||
|
||||
private string getProfilesDir() {
|
||||
return buildPath(this.usersDir, username, "profiles");
|
||||
}
|
||||
|
||||
private string getProfilePath(string name) {
|
||||
return buildPath(this.usersDir, username, "profiles", name ~ ".sqlite");
|
||||
}
|
||||
}
|
||||
|
||||
// API Endpoints Below Here!
|
||||
|
||||
void handleCreateNewProfile(ref HttpRequestContext ctx) {
|
||||
JSONValue obj = ctx.request.readBodyAsJson();
|
||||
string name = obj.object["name"].str;
|
||||
if (!validateProfileName(name)) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
ctx.response.writeBodyString("Invalid profile name.");
|
||||
return;
|
||||
}
|
||||
AuthContext auth = getAuthContext(ctx);
|
||||
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
|
||||
profileRepo.createProfile(name);
|
||||
}
|
||||
|
||||
void handleGetProfiles(ref HttpRequestContext ctx) {
|
||||
AuthContext auth = getAuthContext(ctx);
|
||||
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
|
||||
Profile[] profiles = profileRepo.findAll();
|
||||
ctx.response.writeBodyString(serializeToJson(profiles), "application/json");
|
||||
}
|
||||
|
||||
void handleDeleteProfile(ref HttpRequestContext ctx) {
|
||||
string name = ctx.request.getPathParamAs!string("name");
|
||||
if (!validateProfileName(name)) {
|
||||
ctx.response.status = HttpStatus.BAD_REQUEST;
|
||||
ctx.response.writeBodyString("Invalid profile name.");
|
||||
return;
|
||||
}
|
||||
AuthContext auth = getAuthContext(ctx);
|
||||
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
|
||||
profileRepo.deleteByName(name);
|
||||
}
|
||||
|
||||
void handleGetProperties(ref HttpRequestContext ctx) {
|
||||
ProfileContext profileCtx = getProfileContextOrThrow(ctx);
|
||||
ProfileRepository profileRepo = new FileSystemProfileRepository(profileCtx.user.username);
|
||||
DataSource ds = profileRepo.getDataSource(profileCtx.profile);
|
||||
auto propsRepo = ds.getPropertiesRepository();
|
||||
ProfileProperty[] props = propsRepo.findAll();
|
||||
ctx.response.writeBodyString(serializeToJson(props), "application/json");
|
||||
}
|
||||
|
||||
/// Contextual information that's available when handling requests under a profile.
|
||||
struct ProfileContext {
|
||||
const Profile profile;
|
||||
const User user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get a profile context from a request context. This will attempt to
|
||||
* extract a "profile" path parameter and the authenticated user, and combine
|
||||
* them into the ProfileContext.
|
||||
* Params:
|
||||
* ctx = The request context to read.
|
||||
* Returns: An optional profile context.
|
||||
*/
|
||||
Optional!ProfileContext getProfileContext(ref HttpRequestContext ctx) {
|
||||
import auth.service : AuthContext, getAuthContext;
|
||||
if ("profile" !in ctx.request.pathParams) return Optional!ProfileContext.empty;
|
||||
string profileName = ctx.request.pathParams["profile"];
|
||||
if (!validateProfileName(profileName)) return Optional!ProfileContext.empty;
|
||||
AuthContext authCtx = getAuthContext(ctx);
|
||||
if (authCtx is null) return Optional!ProfileContext.empty;
|
||||
User user = authCtx.user;
|
||||
ProfileRepository repo = new FileSystemProfileRepository("users", user.username);
|
||||
return repo.findByName(profileName)
|
||||
.mapIfPresent!(p => ProfileContext(p, user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `getProfileContext`, but throws an HttpStatusException with a
|
||||
* 404 NOT FOUND status if no profile context could be obtained.
|
||||
* Params:
|
||||
* ctx = The request context to read.
|
||||
* Returns: The profile context that was obtained.
|
||||
*/
|
||||
ProfileContext getProfileContextOrThrow(ref HttpRequestContext ctx) {
|
||||
return getProfileContext(ctx).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
||||
}
|
Loading…
Reference in New Issue