Compare commits

..

10 Commits

Author SHA1 Message Date
Tom
2241b6f529 Added systemd service file 2023-10-03 10:43:18 -06:00
Nathan Anderson
1f5698c3fd Merge branch 'hashing-fix' into 'master'
Hashing fix

See merge request rluv/zerver!1
2023-08-05 02:07:53 +00:00
Nathan Anderson
e1906dd9a8 Fixed password hash and added salt to hash 2023-08-04 20:06:49 -06:00
Tom
7abe33ecaa Added new method in utils for hashing 2023-08-04 19:41:48 -06:00
Nathan Anderson
6bba159e64 Better fix for timezone offset, should add a pass in to the dashboard for timezone offset calculated in client 2023-08-02 11:40:41 -06:00
Nathan Anderson
ef9a7d06e4 Added better datetime and fixed transaction fetching for beginning of month, I think, added edit and delete of transactions and categories 2023-08-02 01:12:32 -06:00
Nathan Anderson
37250a15fb Fix for adding transaction 2023-07-30 22:47:12 -06:00
Nathan Anderson
563b9023dc Added backup and deploy scripts 2023-07-27 12:53:13 -06:00
Nathan Anderson
fb955625bc Fixes for passing in sqlite file name 2023-07-27 12:30:56 -06:00
Nathan Anderson
cdabcf61f3 Added authentication and shared notes endpoints 2023-07-27 01:23:10 -06:00
16 changed files with 3651 additions and 298 deletions

17
data_backup.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
# Get the current timestamp in the format DD-MM-YY-TIME
timestamp=$(date +"%d-%m-%y-%H%M%S")
# Define the source file and the destination directory and file
src="data.db"
dst_dir="backups"
dst_file="data-${timestamp}.db"
# Make backups dir if necessary
mkdir -p $dst_dir
# Move and rename the file
cp "${src}" "${dst_dir}/${dst_file}"
echo "File has been moved to ${dst_dir} and renamed to ${dst_file}"

19
deploy.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
echo 'Creating backup of database file...'
./data_backup.sh
echo 'Rebuilding server...'
sudo systemctl stop zerver
git pull
zig build
echo 'Restarting systemd...'
sudo systemctl restart zerver
systemctl status zerver

1702
src/.deps/datetime.zig Normal file

File diff suppressed because it is too large Load Diff

677
src/.deps/timezones.zig Normal file
View File

@ -0,0 +1,677 @@
// -------------------------------------------------------------------------- //
// Copyright (c) 2019, Jairus Martin. //
// Distributed under the terms of the MIT License. //
// The full license is in the file LICENSE, distributed with this software. //
// -------------------------------------------------------------------------- //
const std = @import("std");
const Timezone = @import("datetime.zig").Timezone;
const create = Timezone.create;
// Timezones
pub const Africa = struct {
pub const Abidjan = create("Africa/Abidjan", 0);
pub const Accra = create("Africa/Accra", 0);
pub const Addis_Ababa = create("Africa/Addis_Ababa", 180);
pub const Algiers = create("Africa/Algiers", 60);
pub const Asmara = create("Africa/Asmara", 180);
pub const Bamako = create("Africa/Bamako", 0);
pub const Bangui = create("Africa/Bangui", 60);
pub const Banjul = create("Africa/Banjul", 0);
pub const Bissau = create("Africa/Bissau", 0);
pub const Blantyre = create("Africa/Blantyre", 120);
pub const Brazzaville = create("Africa/Brazzaville", 60);
pub const Bujumbura = create("Africa/Bujumbura", 120);
pub const Cairo = create("Africa/Cairo", 120);
pub const Casablanca = create("Africa/Casablanca", 60);
pub const Ceuta = create("Africa/Ceuta", 60);
pub const Conakry = create("Africa/Conakry", 0);
pub const Dakar = create("Africa/Dakar", 0);
pub const Dar_es_Salaam = create("Africa/Dar_es_Salaam", 180);
pub const Djibouti = create("Africa/Djibouti", 180);
pub const Douala = create("Africa/Douala", 60);
pub const El_Aaiun = create("Africa/El_Aaiun", 0);
pub const Freetown = create("Africa/Freetown", 0);
pub const Gaborone = create("Africa/Gaborone", 120);
pub const Harare = create("Africa/Harare", 120);
pub const Johannesburg = create("Africa/Johannesburg", 120);
pub const Juba = create("Africa/Juba", 180);
pub const Kampala = create("Africa/Kampala", 180);
pub const Khartoum = create("Africa/Khartoum", 120);
pub const Kigali = create("Africa/Kigali", 120);
pub const Kinshasa = create("Africa/Kinshasa", 60);
pub const Lagos = create("Africa/Lagos", 60);
pub const Libreville = create("Africa/Libreville", 60);
pub const Lome = create("Africa/Lome", 0);
pub const Luanda = create("Africa/Luanda", 60);
pub const Lubumbashi = create("Africa/Lubumbashi", 120);
pub const Lusaka = create("Africa/Lusaka", 120);
pub const Malabo = create("Africa/Malabo", 60);
pub const Maputo = create("Africa/Maputo", 120);
pub const Maseru = create("Africa/Maseru", 120);
pub const Mbabane = create("Africa/Mbabane", 120);
pub const Mogadishu = create("Africa/Mogadishu", 180);
pub const Monrovia = create("Africa/Monrovia", 0);
pub const Nairobi = create("Africa/Nairobi", 180);
pub const Ndjamena = create("Africa/Ndjamena", 60);
pub const Niamey = create("Africa/Niamey", 60);
pub const Nouakchott = create("Africa/Nouakchott", 0);
pub const Ouagadougou = create("Africa/Ouagadougou", 0);
pub const Porto_Novo = create("Africa/Porto-Novo", 60);
pub const Sao_Tome = create("Africa/Sao_Tome", 0);
pub const Timbuktu = create("Africa/Timbuktu", 0);
pub const Tripoli = create("Africa/Tripoli", 120);
pub const Tunis = create("Africa/Tunis", 60);
pub const Windhoek = create("Africa/Windhoek", 120);
};
pub const America = struct {
pub const Adak = create("America/Adak", -600);
pub const Anchorage = create("America/Anchorage", -540);
pub const Anguilla = create("America/Anguilla", -240);
pub const Antigua = create("America/Antigua", -240);
pub const Araguaina = create("America/Araguaina", -180);
pub const Argentina = struct {
pub const Buenos_Aires = create("America/Argentina/Buenos_Aires", -180);
pub const Catamarca = create("America/Argentina/Catamarca", -180);
pub const ComodRivadavia = create("America/Argentina/ComodRivadavia", -180);
pub const Cordoba = create("America/Argentina/Cordoba", -180);
pub const Jujuy = create("America/Argentina/Jujuy", -180);
pub const La_Rioja = create("America/Argentina/La_Rioja", -180);
pub const Mendoza = create("America/Argentina/Mendoza", -180);
pub const Rio_Gallegos = create("America/Argentina/Rio_Gallegos", -180);
pub const Salta = create("America/Argentina/Salta", -180);
pub const San_Juan = create("America/Argentina/San_Juan", -180);
pub const San_Luis = create("America/Argentina/San_Luis", -180);
pub const Tucuman = create("America/Argentina/Tucuman", -180);
pub const Ushuaia = create("America/Argentina/Ushuaia", -180);
};
pub const Aruba = create("America/Aruba", -240);
pub const Asuncion = create("America/Asuncion", -240);
pub const Atikokan = create("America/Atikokan", -300);
pub const Atka = create("America/Atka", -600);
pub const Bahia = create("America/Bahia", -180);
pub const Bahia_Banderas = create("America/Bahia_Banderas", -360);
pub const Barbados = create("America/Barbados", -240);
pub const Belem = create("America/Belem", -180);
pub const Belize = create("America/Belize", -360);
pub const Blanc_Sablon = create("America/Blanc-Sablon", -240);
pub const Boa_Vista = create("America/Boa_Vista", -240);
pub const Bogota = create("America/Bogota", -300);
pub const Boise = create("America/Boise", -420);
pub const Buenos_Aires = create("America/Buenos_Aires", -180);
pub const Cambridge_Bay = create("America/Cambridge_Bay", -420);
pub const Campo_Grande = create("America/Campo_Grande", -240);
pub const Cancun = create("America/Cancun", -300);
pub const Caracas = create("America/Caracas", -240);
pub const Catamarca = create("America/Catamarca", -180);
pub const Cayenne = create("America/Cayenne", -180);
pub const Cayman = create("America/Cayman", -300);
pub const Chicago = create("America/Chicago", -360);
pub const Chihuahua = create("America/Chihuahua", -420);
pub const Coral_Harbour = create("America/Coral_Harbour", -300);
pub const Cordoba = create("America/Cordoba", -180);
pub const Costa_Rica = create("America/Costa_Rica", -360);
pub const Creston = create("America/Creston", -420);
pub const Cuiaba = create("America/Cuiaba", -240);
pub const Curacao = create("America/Curacao", -240);
pub const Danmarkshavn = create("America/Danmarkshavn", 0);
pub const Dawson = create("America/Dawson", -480);
pub const Dawson_Creek = create("America/Dawson_Creek", -420);
pub const Denver = create("America/Denver", -420);
pub const Detroit = create("America/Detroit", -300);
pub const Dominica = create("America/Dominica", -240);
pub const Edmonton = create("America/Edmonton", -420);
pub const Eirunepe = create("America/Eirunepe", -300);
pub const El_Salvador = create("America/El_Salvador", -360);
pub const Ensenada = create("America/Ensenada", -480);
pub const Fort_Nelson = create("America/Fort_Nelson", -420);
pub const Fort_Wayne = create("America/Fort_Wayne", -300);
pub const Fortaleza = create("America/Fortaleza", -180);
pub const Glace_Bay = create("America/Glace_Bay", -240);
pub const Godthab = create("America/Godthab", -180);
pub const Goose_Bay = create("America/Goose_Bay", -240);
pub const Grand_Turk = create("America/Grand_Turk", -300);
pub const Grenada = create("America/Grenada", -240);
pub const Guadeloupe = create("America/Guadeloupe", -240);
pub const Guatemala = create("America/Guatemala", -360);
pub const Guayaquil = create("America/Guayaquil", -300);
pub const Guyana = create("America/Guyana", -240);
pub const Halifax = create("America/Halifax", -240);
pub const Havana = create("America/Havana", -300);
pub const Hermosillo = create("America/Hermosillo", -420);
pub const Indiana = struct {
// FIXME: Name conflict
pub const Indianapolis_ = create("America/Indiana/Indianapolis", -300);
pub const Knox = create("America/Indiana/Knox", -360);
pub const Marengo = create("America/Indiana/Marengo", -300);
pub const Petersburg = create("America/Indiana/Petersburg", -300);
pub const Tell_City = create("America/Indiana/Tell_City", -360);
pub const Vevay = create("America/Indiana/Vevay", -300);
pub const Vincennes = create("America/Indiana/Vincennes", -300);
pub const Winamac = create("America/Indiana/Winamac", -300);
};
pub const Indianapolis = create("America/Indianapolis", -300);
pub const Inuvik = create("America/Inuvik", -420);
pub const Iqaluit = create("America/Iqaluit", -300);
pub const Jamaica = create("America/Jamaica", -300);
pub const Jujuy = create("America/Jujuy", -180);
pub const Juneau = create("America/Juneau", -540);
pub const Kentucky = struct {
// FIXME: Name conflict
pub const Louisville_ = create("America/Kentucky/Louisville", -300);
pub const Monticello = create("America/Kentucky/Monticello", -300);
};
pub const Knox_IN = create("America/Knox_IN", -360);
pub const Kralendijk = create("America/Kralendijk", -240);
pub const La_Paz = create("America/La_Paz", -240);
pub const Lima = create("America/Lima", -300);
pub const Los_Angeles = create("America/Los_Angeles", -480);
pub const Louisville = create("America/Louisville", -300);
pub const Lower_Princes = create("America/Lower_Princes", -240);
pub const Maceio = create("America/Maceio", -180);
pub const Managua = create("America/Managua", -360);
pub const Manaus = create("America/Manaus", -240);
pub const Marigot = create("America/Marigot", -240);
pub const Martinique = create("America/Martinique", -240);
pub const Matamoros = create("America/Matamoros", -360);
pub const Mazatlan = create("America/Mazatlan", -420);
pub const Mendoza = create("America/Mendoza", -180);
pub const Menominee = create("America/Menominee", -360);
pub const Merida = create("America/Merida", -360);
pub const Metlakatla = create("America/Metlakatla", -540);
pub const Mexico_City = create("America/Mexico_City", -360);
pub const Miquelon = create("America/Miquelon", -180);
pub const Moncton = create("America/Moncton", -240);
pub const Monterrey = create("America/Monterrey", -360);
pub const Montevideo = create("America/Montevideo", -180);
pub const Montreal = create("America/Montreal", -300);
pub const Montserrat = create("America/Montserrat", -240);
pub const Nassau = create("America/Nassau", -300);
pub const New_York = create("America/New_York", -300);
pub const Nipigon = create("America/Nipigon", -300);
pub const Nome = create("America/Nome", -540);
pub const Noronha = create("America/Noronha", -120);
pub const North_Dakota = struct {
pub const Beulah = create("America/North_Dakota/Beulah", -360);
pub const Center = create("America/North_Dakota/Center", -360);
pub const New_Salem = create("America/North_Dakota/New_Salem", -360);
};
pub const Ojinaga = create("America/Ojinaga", -420);
pub const Panama = create("America/Panama", -300);
pub const Pangnirtung = create("America/Pangnirtung", -300);
pub const Paramaribo = create("America/Paramaribo", -180);
pub const Phoenix = create("America/Phoenix", -420);
pub const Port_of_Spain = create("America/Port_of_Spain", -240);
pub const Port_au_Prince = create("America/Port-au-Prince", -300);
pub const Porto_Acre = create("America/Porto_Acre", -300);
pub const Porto_Velho = create("America/Porto_Velho", -240);
pub const Puerto_Rico = create("America/Puerto_Rico", -240);
pub const Punta_Arenas = create("America/Punta_Arenas", -180);
pub const Rainy_River = create("America/Rainy_River", -360);
pub const Rankin_Inlet = create("America/Rankin_Inlet", -360);
pub const Recife = create("America/Recife", -180);
pub const Regina = create("America/Regina", -360);
pub const Resolute = create("America/Resolute", -360);
pub const Rio_Branco = create("America/Rio_Branco", -300);
pub const Rosario = create("America/Rosario", -180);
pub const Santa_Isabel = create("America/Santa_Isabel", -480);
pub const Santarem = create("America/Santarem", -180);
pub const Santiago = create("America/Santiago", -240);
pub const Santo_Domingo = create("America/Santo_Domingo", -240);
pub const Sao_Paulo = create("America/Sao_Paulo", -180);
pub const Scoresbysund = create("America/Scoresbysund", -60);
pub const Shiprock = create("America/Shiprock", -420);
pub const Sitka = create("America/Sitka", -540);
pub const St_Barthelemy = create("America/St_Barthelemy", -240);
pub const St_Johns = create("America/St_Johns", -210);
pub const St_Kitts = create("America/St_Kitts", -240);
pub const St_Lucia = create("America/St_Lucia", -240);
pub const St_Thomas = create("America/St_Thomas", -240);
pub const St_Vincent = create("America/St_Vincent", -240);
pub const Swift_Current = create("America/Swift_Current", -360);
pub const Tegucigalpa = create("America/Tegucigalpa", -360);
pub const Thule = create("America/Thule", -240);
pub const Thunder_Bay = create("America/Thunder_Bay", -300);
pub const Tijuana = create("America/Tijuana", -480);
pub const Toronto = create("America/Toronto", -300);
pub const Tortola = create("America/Tortola", -240);
pub const Vancouver = create("America/Vancouver", -480);
pub const Virgin = create("America/Virgin", -240);
pub const Whitehorse = create("America/Whitehorse", -480);
pub const Winnipeg = create("America/Winnipeg", -360);
pub const Yakutat = create("America/Yakutat", -540);
pub const Yellowknife = create("America/Yellowknife", -420);
};
pub const Antarctica = struct {
pub const Casey = create("Antarctica/Casey", 660);
pub const Davis = create("Antarctica/Davis", 420);
pub const DumontDUrville = create("Antarctica/DumontDUrville", 600);
pub const Macquarie = create("Antarctica/Macquarie", 660);
pub const Mawson = create("Antarctica/Mawson", 300);
pub const McMurdo = create("Antarctica/McMurdo", 720);
pub const Palmer = create("Antarctica/Palmer", -180);
pub const Rothera = create("Antarctica/Rothera", -180);
pub const South_Pole = create("Antarctica/South_Pole", 720);
pub const Syowa = create("Antarctica/Syowa", 180);
pub const Troll = create("Antarctica/Troll", 0);
pub const Vostok = create("Antarctica/Vostok", 360);
};
pub const Arctic = struct {
pub const Longyearbyen = create("Arctic/Longyearbyen", 60);
};
pub const Asia = struct {
pub const Aden = create("Asia/Aden", 180);
pub const Almaty = create("Asia/Almaty", 360);
pub const Amman = create("Asia/Amman", 120);
pub const Anadyr = create("Asia/Anadyr", 720);
pub const Aqtau = create("Asia/Aqtau", 300);
pub const Aqtobe = create("Asia/Aqtobe", 300);
pub const Ashgabat = create("Asia/Ashgabat", 300);
pub const Ashkhabad = create("Asia/Ashkhabad", 300);
pub const Atyrau = create("Asia/Atyrau", 300);
pub const Baghdad = create("Asia/Baghdad", 180);
pub const Bahrain = create("Asia/Bahrain", 180);
pub const Baku = create("Asia/Baku", 240);
pub const Bangkok = create("Asia/Bangkok", 420);
pub const Barnaul = create("Asia/Barnaul", 420);
pub const Beirut = create("Asia/Beirut", 120);
pub const Bishkek = create("Asia/Bishkek", 360);
pub const Brunei = create("Asia/Brunei", 480);
pub const Calcutta = create("Asia/Calcutta", 330);
pub const Chita = create("Asia/Chita", 540);
pub const Choibalsan = create("Asia/Choibalsan", 480);
pub const Chongqing = create("Asia/Chongqing", 480);
pub const Chungking = create("Asia/Chungking", 480);
pub const Colombo = create("Asia/Colombo", 330);
pub const Dacca = create("Asia/Dacca", 360);
pub const Damascus = create("Asia/Damascus", 120);
pub const Dhaka = create("Asia/Dhaka", 360);
pub const Dili = create("Asia/Dili", 540);
pub const Dubai = create("Asia/Dubai", 240);
pub const Dushanbe = create("Asia/Dushanbe", 300);
pub const Famagusta = create("Asia/Famagusta", 120);
pub const Gaza = create("Asia/Gaza", 120);
pub const Harbin = create("Asia/Harbin", 480);
pub const Hebron = create("Asia/Hebron", 120);
pub const Ho_Chi_Minh = create("Asia/Ho_Chi_Minh", 420);
pub const Hong_Kong = create("Asia/Hong_Kong", 480);
pub const Hovd = create("Asia/Hovd", 420);
pub const Irkutsk = create("Asia/Irkutsk", 480);
pub const Istanbul = create("Asia/Istanbul", 180);
pub const Jakarta = create("Asia/Jakarta", 420);
pub const Jayapura = create("Asia/Jayapura", 540);
pub const Jerusalem = create("Asia/Jerusalem", 120);
pub const Kabul = create("Asia/Kabul", 270);
pub const Kamchatka = create("Asia/Kamchatka", 720);
pub const Karachi = create("Asia/Karachi", 300);
pub const Kashgar = create("Asia/Kashgar", 360);
pub const Kathmandu = create("Asia/Kathmandu", 345);
pub const Katmandu = create("Asia/Katmandu", 345);
pub const Khandyga = create("Asia/Khandyga", 540);
pub const Kolkata = create("Asia/Kolkata", 330);
pub const Krasnoyarsk = create("Asia/Krasnoyarsk", 420);
pub const Kuala_Lumpur = create("Asia/Kuala_Lumpur", 480);
pub const Kuching = create("Asia/Kuching", 480);
pub const Kuwait = create("Asia/Kuwait", 180);
pub const Macao = create("Asia/Macao", 480);
pub const Macau = create("Asia/Macau", 480);
pub const Magadan = create("Asia/Magadan", 660);
pub const Makassar = create("Asia/Makassar", 480);
pub const Manila = create("Asia/Manila", 480);
pub const Muscat = create("Asia/Muscat", 240);
pub const Nicosia = create("Asia/Nicosia", 120);
pub const Novokuznetsk = create("Asia/Novokuznetsk", 420);
pub const Novosibirsk = create("Asia/Novosibirsk", 420);
pub const Omsk = create("Asia/Omsk", 360);
pub const Oral = create("Asia/Oral", 300);
pub const Phnom_Penh = create("Asia/Phnom_Penh", 420);
pub const Pontianak = create("Asia/Pontianak", 420);
pub const Pyongyang = create("Asia/Pyongyang", 540);
pub const Qatar = create("Asia/Qatar", 180);
pub const Qyzylorda = create("Asia/Qyzylorda", 300);
pub const Rangoon = create("Asia/Rangoon", 390);
pub const Riyadh = create("Asia/Riyadh", 180);
pub const Saigon = create("Asia/Saigon", 420);
pub const Sakhalin = create("Asia/Sakhalin", 660);
pub const Samarkand = create("Asia/Samarkand", 300);
pub const Seoul = create("Asia/Seoul", 540);
pub const Shanghai = create("Asia/Shanghai", 480);
pub const Singapore = create("Asia/Singapore", 480);
pub const Srednekolymsk = create("Asia/Srednekolymsk", 660);
pub const Taipei = create("Asia/Taipei", 480);
pub const Tashkent = create("Asia/Tashkent", 300);
pub const Tbilisi = create("Asia/Tbilisi", 240);
pub const Tehran = create("Asia/Tehran", 210);
pub const Tel_Aviv = create("Asia/Tel_Aviv", 120);
pub const Thimbu = create("Asia/Thimbu", 360);
pub const Thimphu = create("Asia/Thimphu", 360);
pub const Tokyo = create("Asia/Tokyo", 540);
pub const Tomsk = create("Asia/Tomsk", 420);
pub const Ujung_Pandang = create("Asia/Ujung_Pandang", 480);
pub const Ulaanbaatar = create("Asia/Ulaanbaatar", 480);
pub const Ulan_Bator = create("Asia/Ulan_Bator", 480);
pub const Urumqi = create("Asia/Urumqi", 360);
pub const Ust_Nera = create("Asia/Ust-Nera", 600);
pub const Vientiane = create("Asia/Vientiane", 420);
pub const Vladivostok = create("Asia/Vladivostok", 600);
pub const Yakutsk = create("Asia/Yakutsk", 540);
pub const Yangon = create("Asia/Yangon", 390);
pub const Yekaterinburg = create("Asia/Yekaterinburg", 300);
pub const Yerevan = create("Asia/Yerevan", 240);
};
pub const Atlantic = struct {
pub const Azores = create("Atlantic/Azores", -60);
pub const Bermuda = create("Atlantic/Bermuda", -240);
pub const Canary = create("Atlantic/Canary", 0);
pub const Cape_Verde = create("Atlantic/Cape_Verde", -60);
pub const Faeroe = create("Atlantic/Faeroe", 0);
pub const Faroe = create("Atlantic/Faroe", 0);
pub const Jan_Mayen = create("Atlantic/Jan_Mayen", 60);
pub const Madeira = create("Atlantic/Madeira", 0);
pub const Reykjavik = create("Atlantic/Reykjavik", 0);
pub const South_Georgia = create("Atlantic/South_Georgia", -120);
pub const St_Helena = create("Atlantic/St_Helena", 0);
pub const Stanley = create("Atlantic/Stanley", -180);
};
pub const Australia = struct {
pub const ACT = create("Australia/ACT", 600);
pub const Adelaide = create("Australia/Adelaide", 570);
pub const Brisbane = create("Australia/Brisbane", 600);
pub const Broken_Hill = create("Australia/Broken_Hill", 570);
pub const Canberra = create("Australia/Canberra", 600);
pub const Currie = create("Australia/Currie", 600);
pub const Darwin = create("Australia/Darwin", 570);
pub const Eucla = create("Australia/Eucla", 525);
pub const Hobart = create("Australia/Hobart", 600);
pub const LHI = create("Australia/LHI", 630);
pub const Lindeman = create("Australia/Lindeman", 600);
pub const Lord_Howe = create("Australia/Lord_Howe", 630);
pub const Melbourne = create("Australia/Melbourne", 600);
pub const North = create("Australia/North", 570);
pub const NSW = create("Australia/NSW", 600);
pub const Perth = create("Australia/Perth", 480);
pub const Queensland = create("Australia/Queensland", 600);
pub const South = create("Australia/South", 570);
pub const Sydney = create("Australia/Sydney", 600);
pub const Tasmania = create("Australia/Tasmania", 600);
pub const Victoria = create("Australia/Victoria", 600);
pub const West = create("Australia/West", 480);
pub const Yancowinna = create("Australia/Yancowinna", 570);
};
pub const Brazil = struct {
pub const Acre = create("Brazil/Acre", -300);
pub const DeNoronha = create("Brazil/DeNoronha", -120);
pub const East = create("Brazil/East", -180);
pub const West = create("Brazil/West", -240);
};
pub const Canada = struct {
pub const Atlantic = create("Canada/Atlantic", -240);
pub const Central = create("Canada/Central", -360);
pub const Eastern = create("Canada/Eastern", -300);
pub const Mountain = create("Canada/Mountain", -420);
pub const Newfoundland = create("Canada/Newfoundland", -210);
pub const Pacific = create("Canada/Pacific", -480);
pub const Saskatchewan = create("Canada/Saskatchewan", -360);
pub const Yukon = create("Canada/Yukon", -480);
};
pub const CET = create("CET", 60);
pub const Chile = struct {
pub const Continental = create("Chile/Continental", -240);
pub const EasterIsland = create("Chile/EasterIsland", -360);
};
pub const CST6CDT = create("CST6CDT", -360);
pub const Cuba = create("Cuba", -300);
pub const EET = create("EET", 120);
pub const Egypt = create("Egypt", 120);
pub const Eire = create("Eire", 0);
pub const EST = create("EST", -300);
pub const EST5EDT = create("EST5EDT", -300);
pub const Etc = struct {
// NOTE: The signs are intentionally inverted. See the Etc area description.
pub const GMT = create("Etc/GMT", 0);
pub const GMTp0 = create("Etc/GMT+0", 0);
pub const GMTp1 = create("Etc/GMT+1", -60);
pub const GMTp10 = create("Etc/GMT+10", -600);
pub const GMTp11 = create("Etc/GMT+11", -660);
pub const GMTp12 = create("Etc/GMT+12", -720);
pub const GMTp2 = create("Etc/GMT+2", -120);
pub const GMTp3 = create("Etc/GMT+3", -180);
pub const GMTp4 = create("Etc/GMT+4", -240);
pub const GMTp5 = create("Etc/GMT+5", -300);
pub const GMTp6 = create("Etc/GMT+6", -360);
pub const GMTp7 = create("Etc/GMT+7", -420);
pub const GMTp8 = create("Etc/GMT+8", -480);
pub const GMTp9 = create("Etc/GMT+9", -540);
pub const GMT0 = create("Etc/GMT0", 0);
pub const GMTm0 = create("Etc/GMT-0", 0);
pub const GMTm1 = create("Etc/GMT-1", 60);
pub const GMTm10 = create("Etc/GMT-10", 600);
pub const GMTm11 = create("Etc/GMT-11", 660);
pub const GMTm12 = create("Etc/GMT-12", 720);
pub const GMTm13 = create("Etc/GMT-13", 780);
pub const GMTm14 = create("Etc/GMT-14", 840);
pub const GMTm2 = create("Etc/GMT-2", 120);
pub const GMTm3 = create("Etc/GMT-3", 180);
pub const GMTm4 = create("Etc/GMT-4", 240);
pub const GMTm5 = create("Etc/GMT-5", 300);
pub const GMTm6 = create("Etc/GMT-6", 360);
pub const GMTm7 = create("Etc/GMT-7", 420);
pub const GMTm8 = create("Etc/GMT-8", 480);
pub const GMTm9 = create("Etc/GMT-9", 540);
pub const Greenwich = create("Etc/Greenwich", 0);
pub const UCT = create("Etc/UCT", 0);
pub const Universal = create("Etc/Universal", 0);
pub const UTC = create("Etc/UTC", 0);
pub const Zulu = create("Etc/Zulu", 0);
};
pub const Europe = struct {
pub const Amsterdam = create("Europe/Amsterdam", 60);
pub const Andorra = create("Europe/Andorra", 60);
pub const Astrakhan = create("Europe/Astrakhan", 240);
pub const Athens = create("Europe/Athens", 120);
pub const Belfast = create("Europe/Belfast", 0);
pub const Belgrade = create("Europe/Belgrade", 60);
pub const Berlin = create("Europe/Berlin", 60);
pub const Bratislava = create("Europe/Bratislava", 60);
pub const Brussels = create("Europe/Brussels", 60);
pub const Bucharest = create("Europe/Bucharest", 120);
pub const Budapest = create("Europe/Budapest", 60);
pub const Busingen = create("Europe/Busingen", 60);
pub const Chisinau = create("Europe/Chisinau", 120);
pub const Copenhagen = create("Europe/Copenhagen", 60);
pub const Dublin = create("Europe/Dublin", 0);
pub const Gibraltar = create("Europe/Gibraltar", 60);
pub const Guernsey = create("Europe/Guernsey", 0);
pub const Helsinki = create("Europe/Helsinki", 120);
pub const Isle_of_Man = create("Europe/Isle_of_Man", 0);
pub const Istanbul = create("Europe/Istanbul", 180);
pub const Jersey = create("Europe/Jersey", 0);
pub const Kaliningrad = create("Europe/Kaliningrad", 120);
pub const Kiev = create("Europe/Kiev", 120);
pub const Kirov = create("Europe/Kirov", 180);
pub const Lisbon = create("Europe/Lisbon", 0);
pub const Ljubljana = create("Europe/Ljubljana", 60);
pub const London = create("Europe/London", 0);
pub const Luxembourg = create("Europe/Luxembourg", 60);
pub const Madrid = create("Europe/Madrid", 60);
pub const Malta = create("Europe/Malta", 60);
pub const Mariehamn = create("Europe/Mariehamn", 120);
pub const Minsk = create("Europe/Minsk", 180);
pub const Monaco = create("Europe/Monaco", 60);
pub const Moscow = create("Europe/Moscow", 180);
pub const Oslo = create("Europe/Oslo", 60);
pub const Paris = create("Europe/Paris", 60);
pub const Podgorica = create("Europe/Podgorica", 60);
pub const Prague = create("Europe/Prague", 60);
pub const Riga = create("Europe/Riga", 120);
pub const Rome = create("Europe/Rome", 60);
pub const Samara = create("Europe/Samara", 240);
pub const San_Marino = create("Europe/San_Marino", 60);
pub const Sarajevo = create("Europe/Sarajevo", 60);
pub const Saratov = create("Europe/Saratov", 240);
pub const Simferopol = create("Europe/Simferopol", 180);
pub const Skopje = create("Europe/Skopje", 60);
pub const Sofia = create("Europe/Sofia", 120);
pub const Stockholm = create("Europe/Stockholm", 60);
pub const Tallinn = create("Europe/Tallinn", 120);
pub const Tirane = create("Europe/Tirane", 60);
pub const Tiraspol = create("Europe/Tiraspol", 120);
pub const Ulyanovsk = create("Europe/Ulyanovsk", 240);
pub const Uzhgorod = create("Europe/Uzhgorod", 120);
pub const Vaduz = create("Europe/Vaduz", 60);
pub const Vatican = create("Europe/Vatican", 60);
pub const Vienna = create("Europe/Vienna", 60);
pub const Vilnius = create("Europe/Vilnius", 120);
pub const Volgograd = create("Europe/Volgograd", 240);
pub const Warsaw = create("Europe/Warsaw", 60);
pub const Zagreb = create("Europe/Zagreb", 60);
pub const Zaporozhye = create("Europe/Zaporozhye", 120);
pub const Zurich = create("Europe/Zurich", 60);
};
pub const GB = create("GB", 0);
pub const GB_Eire = create("GB-Eire", 0);
pub const GMT = create("GMT", 0);
pub const GMTp0 = create("GMT+0", 0);
pub const GMT0 = create("GMT0", 0);
pub const GMTm0 = create("GMT-0", 0);
pub const Greenwich = create("Greenwich", 0);
pub const Hongkong = create("Hongkong", 480);
pub const HST = create("HST", -600);
pub const Iceland = create("Iceland", 0);
pub const Indian = struct {
pub const Antananarivo = create("Indian/Antananarivo", 180);
pub const Chagos = create("Indian/Chagos", 360);
pub const Christmas = create("Indian/Christmas", 420);
pub const Cocos = create("Indian/Cocos", 390);
pub const Comoro = create("Indian/Comoro", 180);
pub const Kerguelen = create("Indian/Kerguelen", 300);
pub const Mahe = create("Indian/Mahe", 240);
pub const Maldives = create("Indian/Maldives", 300);
pub const Mauritius = create("Indian/Mauritius", 240);
pub const Mayotte = create("Indian/Mayotte", 180);
pub const Reunion = create("Indian/Reunion", 240);
};
pub const Iran = create("Iran", 210);
pub const Israel = create("Israel", 120);
pub const Jamaica = create("Jamaica", -300);
pub const Japan = create("Japan", 540);
pub const Kwajalein = create("Kwajalein", 720);
pub const Libya = create("Libya", 120);
pub const MET = create("MET", 60);
pub const Mexico = struct {
pub const BajaNorte = create("Mexico/BajaNorte", -480);
pub const BajaSur = create("Mexico/BajaSur", -420);
pub const General = create("Mexico/General", -360);
};
pub const MST = create("MST", -420);
pub const MST7MDT = create("MST7MDT", -420);
pub const Navajo = create("Navajo", -420);
pub const NZ = create("NZ", 720);
pub const NZ_CHAT = create("NZ-CHAT", 765);
pub const Pacific = struct {
pub const Apia = create("Pacific/Apia", 780);
pub const Auckland = create("Pacific/Auckland", 720);
pub const Bougainville = create("Pacific/Bougainville", 660);
pub const Chatham = create("Pacific/Chatham", 765);
pub const Chuuk = create("Pacific/Chuuk", 600);
pub const Easter = create("Pacific/Easter", -360);
pub const Efate = create("Pacific/Efate", 660);
pub const Enderbury = create("Pacific/Enderbury", 780);
pub const Fakaofo = create("Pacific/Fakaofo", 780);
pub const Fiji = create("Pacific/Fiji", 720);
pub const Funafuti = create("Pacific/Funafuti", 720);
pub const Galapagos = create("Pacific/Galapagos", -360);
pub const Gambier = create("Pacific/Gambier", -540);
pub const Guadalcanal = create("Pacific/Guadalcanal", 660);
pub const Guam = create("Pacific/Guam", 600);
pub const Honolulu = create("Pacific/Honolulu", -600);
pub const Johnston = create("Pacific/Johnston", -600);
pub const Kiritimati = create("Pacific/Kiritimati", 840);
pub const Kosrae = create("Pacific/Kosrae", 660);
pub const Kwajalein = create("Pacific/Kwajalein", 720);
pub const Majuro = create("Pacific/Majuro", 720);
pub const Marquesas = create("Pacific/Marquesas", -570);
pub const Midway = create("Pacific/Midway", -660);
pub const Nauru = create("Pacific/Nauru", 720);
pub const Niue = create("Pacific/Niue", -660);
pub const Norfolk = create("Pacific/Norfolk", 660);
pub const Noumea = create("Pacific/Noumea", 660);
pub const Pago_Pago = create("Pacific/Pago_Pago", -660);
pub const Palau = create("Pacific/Palau", 540);
pub const Pitcairn = create("Pacific/Pitcairn", -480);
pub const Pohnpei = create("Pacific/Pohnpei", 660);
pub const Ponape = create("Pacific/Ponape", 660);
pub const Port_Moresby = create("Pacific/Port_Moresby", 600);
pub const Rarotonga = create("Pacific/Rarotonga", -600);
pub const Saipan = create("Pacific/Saipan", 600);
pub const Samoa = create("Pacific/Samoa", -660);
pub const Tahiti = create("Pacific/Tahiti", -600);
pub const Tarawa = create("Pacific/Tarawa", 720);
pub const Tongatapu = create("Pacific/Tongatapu", 780);
pub const Truk = create("Pacific/Truk", 600);
pub const Wake = create("Pacific/Wake", 720);
pub const Wallis = create("Pacific/Wallis", 720);
pub const Yap = create("Pacific/Yap", 600);
};
pub const Poland = create("Poland", 60);
pub const Portugal = create("Portugal", 0);
pub const PRC = create("PRC", 480);
pub const PST8PDT = create("PST8PDT", -480);
pub const ROC = create("ROC", 480);
pub const ROK = create("ROK", 540);
pub const Singapore = create("Singapore", 480);
pub const Turkey = create("Turkey", 180);
pub const UCT = create("UCT", 0);
pub const Universal = create("Universal", 0);
pub const US = struct {
pub const Alaska = create("US/Alaska", -540);
pub const Aleutian = create("US/Aleutian", -600);
pub const Arizona = create("US/Arizona", -420);
pub const Central = create("US/Central", -360);
pub const Eastern = create("US/Eastern", -300);
pub const East_Indiana = create("US/East-Indiana", -300);
pub const Hawaii = create("US/Hawaii", -600);
pub const Indiana_Starke = create("US/Indiana-Starke", -360);
pub const Michigan = create("US/Michigan", -300);
pub const Mountain = create("US/Mountain", -420);
pub const Pacific = create("US/Pacific", -480);
pub const Pacific_New = create("US/Pacific-New", -480);
pub const Samoa = create("US/Samoa", -660);
};
pub const UTC = create("UTC", 0);
pub const WET = create("WET", 0);
pub const W_SU = create("W-SU", 180);
pub const Zulu = create("Zulu", 0);
// TODO: Allow lookup by name
//pub fn getAll() []*const Timezone {
// for (comptime std.meta.fields(@This())) |field {
//
// }
//}
//pub fn get(name: []const u8) ?*const Timezone {
// return ALL_TIMEZONES.getValue(name);
//}
test "timezone-get" {
const testing = std.testing;
//try testing.expect(get("America/New_York").? == America.New_York);
try testing.expect(America.New_York.offset == -300);
}

View File

@ -11,9 +11,11 @@ pub const Db = struct {
_mode: ?sqlite.Db.Mode, _mode: ?sqlite.Db.Mode,
_sql_db: sqlite.Db, _sql_db: sqlite.Db,
pub fn init(allocator: Allocator, mode: ?sqlite.Db.Mode) !Db { pub fn init(allocator: Allocator, db_path: ?[]const u8, mode: ?sqlite.Db.Mode) !Db {
const path: [:0]const u8 = if (db_path != null) try std.mem.Allocator.dupeZ(allocator, u8, db_path.?) else "./data.db";
defer allocator.free(path);
var sqlDb = try sqlite.Db.init(.{ var sqlDb = try sqlite.Db.init(.{
.mode = if (mode != null) mode.? else sqlite.Db.Mode{ .File = "./data.db" }, .mode = if (mode != null) mode.? else sqlite.Db.Mode{ .File = path },
.open_flags = .{ .open_flags = .{
.write = true, .write = true,
.create = true, .create = true,
@ -31,15 +33,51 @@ pub const Db = struct {
self: *Db, self: *Db,
comptime Type: type, comptime Type: type,
allocator: Allocator, allocator: Allocator,
comptime whereClause: []const u8, comptime where_clause: []const u8,
values: anytype, values: anytype,
comptime limit: ?u32, comptime order_by_field: ?[]const u8,
comptime order: ?[]const u8,
comptime limit: ?bool,
limit_val: ?u32,
) !?[]Type { ) !?[]Type {
_ = limit; comptime {
if (order == null and order_by_field != null or order != null and order_by_field == null) {
@compileError("Must provide both order and order_by or neither, with select " ++ where_clause);
}
if (order != null) {
if (!(std.mem.eql(u8, order.?, "DESC") or std.mem.eql(u8, order.?, "ASC"))) {
@compileError("Must use ASC or DESC for order_by, used: " ++ order.?);
}
}
if (!std.mem.containsAtLeast(u8, where_clause, 1, "?")) {
@compileError("where_clause missing '?', no possible values to insert " ++ where_clause);
}
// Check that where_clause only contains fields in struct
var query_objs_iter = std.mem.split(u8, where_clause, "?");
inline for (@typeInfo(@TypeOf(values)).Struct.fields) |struct_field| {
const name = struct_field.name;
const query_obj = query_objs_iter.next();
if (query_obj == null) {
@compileError("Query does not have enough clauses for passed in data: " ++ where_clause);
}
if (std.mem.containsAtLeast(u8, query_obj.?, 1, name)) {
continue;
} else {
@compileError("Missing field or messed up order in select:\n" ++ where_clause ++ "\n" ++ query_obj.?);
}
}
const last = query_objs_iter.next();
if (last != null and !std.mem.eql(u8, last.?, "")) {
@compileError("Values is lacking, query contains more ? than data provided: " ++ where_clause ++ "\nLeft with: " ++ last.?);
}
}
if (limit == null and limit_val != null or limit != null and limit_val == null) {
std.log.err("Must provide both limit and limit_val or neither, with select: {s}", .{where_clause});
return null;
}
var res_array: std.ArrayList(Type) = std.ArrayList(Type).init(allocator); var res_array: std.ArrayList(Type) = std.ArrayList(Type).init(allocator);
if (order_by_field == null and limit == null) {
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ whereClause ++ ";"; const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ where_clause ++ ";";
var stmt = try self._sql_db.prepare(query); var stmt = try self._sql_db.prepare(query);
defer stmt.deinit(); defer stmt.deinit();
@ -47,6 +85,34 @@ pub const Db = struct {
while (try iter.nextAlloc(allocator, .{})) |row| { while (try iter.nextAlloc(allocator, .{})) |row| {
try res_array.append(row); try res_array.append(row);
} }
} else if (order_by_field == null and limit != null) {
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ where_clause ++ " LIMIT ?;";
var stmt = try self._sql_db.prepare(query);
defer stmt.deinit();
var iter = try stmt.iteratorAlloc(Type, allocator, utils.structConcatFields(values, .{ .limit = limit_val.? }));
while (try iter.nextAlloc(allocator, .{})) |row| {
try res_array.append(row);
}
} else if (order_by_field != null and limit == null) {
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ where_clause ++ " ORDER BY " ++ order_by_field.? ++ " " ++ order.? ++ ";";
var stmt = try self._sql_db.prepare(query);
defer stmt.deinit();
var iter = try stmt.iteratorAlloc(Type, allocator, values);
while (try iter.nextAlloc(allocator, .{})) |row| {
try res_array.append(row);
}
} else {
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ where_clause ++ " ORDER BY " ++ order_by_field.? ++ " " ++ order.? ++ " LIMIT ?;";
var stmt = try self._sql_db.prepare(query);
defer stmt.deinit();
var iter = try stmt.iteratorAlloc(Type, allocator, utils.structConcatFields(values, .{ .limit = limit_val.? }));
while (try iter.nextAlloc(allocator, .{})) |row| {
try res_array.append(row);
}
}
return try res_array.toOwnedSlice(); return try res_array.toOwnedSlice();
} }
@ -57,6 +123,21 @@ pub const Db = struct {
} }
pub fn selectOne(self: *Db, comptime Type: type, allocator: Allocator, comptime query: []const u8, values: anytype) !?Type { pub fn selectOne(self: *Db, comptime Type: type, allocator: Allocator, comptime query: []const u8, values: anytype) !?Type {
comptime {
var query_objs_iter = std.mem.split(u8, query, "=");
inline for (@typeInfo(@TypeOf(values)).Struct.fields) |struct_field| {
const name = struct_field.name;
const query_obj = query_objs_iter.next();
if (query_obj == null) {
@compileError("Query does not have enough clauses for passed in data:\n" ++ "Type: " ++ @typeName(Type) ++ "\n" ++ query ++ "\n");
}
if (std.mem.containsAtLeast(u8, query_obj.?, 1, name)) {
continue;
} else {
@compileError("Missing field or messed up order in select:\n" ++ query ++ "\n" ++ query_obj.?);
}
}
}
const row = try self._sql_db.oneAlloc(Type, allocator, query, .{}, values); const row = try self._sql_db.oneAlloc(Type, allocator, query, .{}, values);
// std.debug.print("{any}", .{row}); // std.debug.print("{any}", .{row});
return row; return row;
@ -71,10 +152,35 @@ pub const Db = struct {
}; };
} }
pub fn updateHideById(self: *Db, comptime Type: type, comptime hide: bool, id: u32) !void {
const hide_val = if (hide) 1 else 0;
const now = @intCast(u64, std.time.milliTimestamp());
self._sql_db.exec(models.createHideQuery(Type), .{}, .{ .hide = hide_val, .updated_at = now, .id = id }) catch |err| {
std.log.err("Encountered error while updating hide on model {s}", .{@typeName(Type)});
return err;
};
}
pub fn insert(self: *Db, comptime Type: type, values: anytype) !void { pub fn insert(self: *Db, comptime Type: type, values: anytype) !void {
// TODO check there is an ID field comptime {
const query = models.createInsertQuery(Type);
// const InsertType = utils.removeStructFields(Type, &[_]u8{0});
var query_objs_iter = std.mem.split(u8, query, ",");
inline for (@typeInfo(@TypeOf(values)).Struct.fields) |struct_field| {
const name = struct_field.name;
const query_obj = query_objs_iter.next();
if (query_obj == null) {
@compileError("Query does not have enough clauses for passed in data:\n" ++ "Type: " ++ @typeName(Type) ++ "\n" ++ query ++ "\n");
}
if (std.mem.containsAtLeast(u8, query_obj.?, 1, name)) {
continue;
} else {
@compileError("Missing field or messed up order in insert:\n" ++ query ++ "\n" ++ query_obj.? ++ "\n");
}
}
}
self._sql_db.exec(models.createInsertQuery(Type), .{}, values) catch |err| { self._sql_db.exec(models.createInsertQuery(Type), .{}, values) catch |err| {
std.debug.print("Encountered error while inserting data:\n{any}\n\tQuery:{s}\n{any}\n", .{ values, models.createInsertQuery(Type), err }); std.debug.print("Encountered error while inserting data:\n\t{any}\nQuery:\t{s}\n{any}\n", .{ values, models.createInsertQuery(Type), err });
return err; return err;
}; };
} }

View File

@ -1,5 +1,31 @@
const std = @import("std"); const std = @import("std");
const utils = @import("../utils.zig");
// fn UniqueSQLType(comptime ValType: type) type {
// comptime {
// _ = switch (@typeInfo(ValType)) {
// .Int, .Float, .Pointer => true,
// else => {
// @compileError("An invalid type passed to unique sql contraint type: " ++ @typeName(ValType));
// },
// };
// }
// comptime {
// const t = switch (@typeInfo(ValType)) {
// .Int => @Type(.{ .Int = .{ .signedness = @typeInfo(ValType).Int.signedness, .bits = @typeInfo(ValType).Int.bits } }),
// .Float => @Type(.{ .Float = .{ .bits = @typeInfo(ValType).Float.bits } }),
// .Pointer => {
// const pt = @typeInfo(ValType).Pointer;
// // _ = std.fmt.comptimePrint("Pointer type info: {any}", .{pt});
// return @Type(.{ .Pointer = .{ .size = pt.size, .is_const = pt.is_const, .is_volatile = pt.is_volatile, .alignment = pt.alignment, .address_space = pt.address_space, .child = pt.child, .is_allowzero = pt.is_allowzero, .sentinel = pt.sentinel } });
// },
// else => unreachable,
// };
// return t;
// }
// // @setEvalBranchQuota(additional_fields.len * fields.len * 10);
// }
// pub const UniqueType = union(enum) { string: []const u8, real: f64 };
pub const Transaction = struct { pub const Transaction = struct {
id: u32, id: u32,
@ -7,7 +33,7 @@ pub const Transaction = struct {
type: []const u8, type: []const u8,
memo: ?[]const u8, memo: ?[]const u8,
budget_id: u32, budget_id: u32,
added_by_user_id: u32, created_by_user_id: u32,
budget_category_id: ?u32, budget_category_id: ?u32,
date: u64, date: u64,
created_at: u64, created_at: u64,
@ -28,15 +54,20 @@ pub const BudgetCategory = struct {
pub const Budget = struct { pub const Budget = struct {
id: u32, id: u32,
family_id: u32,
name: []const u8, name: []const u8,
created_at: u64, created_at: u64,
updated_at: u64, updated_at: u64,
hide: u8, hide: u8,
expected_income: ?f64,
}; };
pub const User = struct { pub const User = struct {
id: u32, id: u32,
name: []const u8, name: []const u8,
username: ?[]const u8,
email: ?[]const u8,
pass_hash: u32,
family_id: u32, family_id: u32,
budget_id: u32, budget_id: u32,
created_at: u64, created_at: u64,
@ -47,13 +78,45 @@ pub const User = struct {
pub const Family = struct { pub const Family = struct {
id: u32, id: u32,
budget_id: u32, code: ?[]const u8,
hide: u8,
created_at: u64, created_at: u64,
updated_at: u64, updated_at: u64,
hide: u8,
}; };
pub const ModelTypes = [5]type{ Transaction, BudgetCategory, Budget, User, Family }; pub const SharedNote = struct {
id: u32,
family_id: u32,
created_by_user_id: u32,
content: []const u8,
title: []const u8,
color: ?[]const u8,
tag_ids: []const u8,
is_markdown: u2,
created_at: u64,
updated_at: u64,
hide: u8,
};
pub const Tag = struct {
id: u32,
family_id: u32,
created_by_user_id: u32,
name: []const u8,
type: []const u8,
created_at: u64,
updated_at: u64,
hide: u8,
};
pub const Token = struct {
user_id: u32,
family_id: u32,
generated_at: u64,
expires_at: u64,
};
pub const ModelTypes = [_]type{ Transaction, BudgetCategory, Budget, User, Family, SharedNote, Tag };
/// Functions for creating SQLite queries for any models above /// Functions for creating SQLite queries for any models above
pub inline fn createSelectOnIdQuery(comptime Type: type) []const u8 { pub inline fn createSelectOnIdQuery(comptime Type: type) []const u8 {
@ -67,7 +130,11 @@ pub inline fn createSelectOnFieldQuery(
comptime comparator: []const u8, comptime comparator: []const u8,
) ![]const u8 { ) ![]const u8 {
comptime { comptime {
try std.testing.expect(fieldName == null and structField != null or fieldName != null and structField == null); if (fieldName == null and structField == null) {
@compileError("Must provide struct and fieldname");
} else if (fieldName != null and structField != null) {
@compileError("Cannot provide struct and fieldname");
}
var field: []const u8 = undefined; var field: []const u8 = undefined;
if (structField != null) { if (structField != null) {
field = structField.?; field = structField.?;
@ -79,6 +146,19 @@ pub inline fn createSelectOnFieldQuery(
} }
} }
pub inline fn createRawSelectQuery(comptime Type: type, comptime where_query: []const u8) ![]const u8 {
comptime {
if (!std.mem.containsAtLeast(u8, where_query, 1, "WHERE")) {
@compileError("Provided where query does not contain 'WHERE' string: " ++ where_query);
}
if (!std.mem.endsWith(u8, where_query, ";")) {
@compileError("Where query does not end with semicolon: " ++ where_query);
}
var query = "SELECT * FROM " ++ getTypeTableName(Type) ++ " " ++ where_query;
return query;
}
}
pub inline fn createDeleteOnIdQuery(comptime Type: type) []const u8 { pub inline fn createDeleteOnIdQuery(comptime Type: type) []const u8 {
return "DELETE from " ++ getTypeTableName(Type) ++ " WHERE id = ?;"; return "DELETE from " ++ getTypeTableName(Type) ++ " WHERE id = ?;";
} }
@ -89,14 +169,15 @@ pub inline fn createInsertQuery(comptime Type: type) []const u8 {
var qs: []const u8 = "?"; var qs: []const u8 = "?";
inline for (@typeInfo(Type).Struct.fields, 0..) |field, i| { inline for (@typeInfo(Type).Struct.fields, 0..) |field, i| {
// This is brittle, assumes 'id' struct field is first // This is brittle, assumes 'id' struct field is first
if (std.mem.eql(u8, field.name, "id")) {
continue;
}
if (i > 1) { if (i > 1) {
query = query ++ ", "; query = query ++ ", ";
qs = qs ++ ", ?"; qs = qs ++ ", ?";
} }
if (i != 0) {
query = query ++ field.name; query = query ++ field.name;
} }
}
query = query ++ ") VALUES (" ++ qs ++ ");"; query = query ++ ") VALUES (" ++ qs ++ ");";
return query; return query;
} }
@ -116,6 +197,23 @@ pub inline fn createUpdateQuery(comptime Type: type) []const u8 {
} }
} }
pub fn createHideQuery(comptime Type: type) []const u8 {
comptime {
var has_hide = false;
inline for (@typeInfo(Type).Struct.fields) |field| {
if (std.mem.eql(u8, field.name, "hide")) {
has_hide = true;
}
}
if (!has_hide) {
@compileError("Model does not have hide field, cannot create hide query: " ++ @typeName(Type));
}
return "UPDATE " ++ getTypeTableName(Type) ++ " SET hide = ?, updated_at = ? WHERE id = ?;";
}
}
pub inline fn createTableDeleteQuery(comptime Type: type) []const u8 { pub inline fn createTableDeleteQuery(comptime Type: type) []const u8 {
return "DROP TABLE IF EXISTS " ++ getTypeTableName(Type) ++ ";"; return "DROP TABLE IF EXISTS " ++ getTypeTableName(Type) ++ ";";
} }
@ -139,6 +237,11 @@ pub inline fn createTableMigrationQuery(comptime Type: type) []const u8 {
inline fn getSQLiteColumnMigrateText(comptime struct_field: std.builtin.Type.StructField) []const u8 { inline fn getSQLiteColumnMigrateText(comptime struct_field: std.builtin.Type.StructField) []const u8 {
comptime { comptime {
if (std.mem.eql(u8, struct_field.name, "id")) return "INTEGER PRIMARY KEY AUTOINCREMENT"; if (std.mem.eql(u8, struct_field.name, "id")) return "INTEGER PRIMARY KEY AUTOINCREMENT";
if (std.mem.eql(u8, @typeName(struct_field.type), "UniqueSQLType")) {
@compileLog(struct_field.type);
}
// _ = std.fmt.comptimePrint("Got type {any}", .{@typeInfo(struct_field.type)});
const val = switch (@typeInfo(struct_field.type)) { const val = switch (@typeInfo(struct_field.type)) {
.Int => "INTEGER NOT NULL", .Int => "INTEGER NOT NULL",
.Float => "REAL NOT NULL", .Float => "REAL NOT NULL",
@ -151,7 +254,14 @@ inline fn getSQLiteColumnMigrateText(comptime struct_field: std.builtin.Type.Str
}, },
.Array => "TEXT NOT NULL", .Array => "TEXT NOT NULL",
.Pointer => "TEXT NOT NULL", .Pointer => "TEXT NOT NULL",
else => unreachable,
// .Struct => {
// if (std.mem.eql(u8, @typeName(struct_field.type), "UniqueSQLString") or std.mem.eql(u8, @typeName(struct_field.type), "UniqueSQLString"))
// return "THIS";
// },
else => {
@compileError("Passed in a type that has no sql migration defined: " ++ @typeName(struct_field.type));
},
}; };
return val; return val;
} }
@ -165,7 +275,14 @@ pub inline fn getTypeTableName(comptime Type: type) []const u8 {
Budget => "budgets", Budget => "budgets",
BudgetCategory => "budget_categories", BudgetCategory => "budget_categories",
Family => "families", Family => "families",
else => unreachable, SharedNote => "shared_notes",
Tag => "tags",
// Tag => "tags",
else => {
@compileError("Missing a table name, check to make sure that each model in the models list has a table name provided here.\n" ++ @typeName(Type));
// _ = std.fmt.comptimePrint("Missing a table name, check to make sure that each model in the models list has a table name provided here.\n{any}", .{ModelTypes});
// unreachable;
},
}; };
} }
} }

View File

@ -6,24 +6,55 @@ const ztime = @import(".deps/time.zig");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const budget = @import("routes/budget.zig"); const budget = @import("routes/budget.zig");
const auth = @import("routes/auth.zig");
const user = @import("routes/user.zig"); const user = @import("routes/user.zig");
const trans = @import("routes/transactions.zig"); const trans = @import("routes/transactions.zig");
const dash = @import("routes/dashboard.zig"); const dash = @import("routes/dashboard.zig");
const note = @import("routes/shared_note.zig");
const Db = @import("db/db.zig").Db; const Db = @import("db/db.zig").Db;
var db: Db = undefined; var db: ?Db = null;
pub fn getDb() *Db { pub fn getDb() *Db {
return &db; return &db.?;
} }
pub fn startHttpServer() !void { pub fn startHttpServer() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
db = try Db.init(allocator, null); // db = try Db.init(allocator, null);
defer db.deinit(); // defer db.deinit();
var args = try std.process.argsWithAllocator(allocator);
// skip program name
_ = args.skip();
while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "--db_path")) {
const path = args.next();
// std.debug.print("Got path: {any}", .{path});
if (path) |db_path| {
db = try Db.init(allocator, db_path, null);
} else {
std.log.err("Db path not provided after arg", .{});
return;
}
}
if (std.mem.eql(u8, arg, "--make-migration")) {
if (db == null) {
std.log.err("Cannot migrate, provide db path first", .{});
return;
}
try db.?.wipeAndMigrateDb();
}
}
if (db == null) {
db = try Db.init(allocator, null, null);
}
defer db.?.deinit();
var server = try httpz.Server().init(allocator, .{ .port = 8081 }); var server = try httpz.Server().init(allocator, .{ .port = 8081 });
@ -35,22 +66,30 @@ pub fn startHttpServer() !void {
var router = server.router(); var router = server.router();
router.get("/user/:id", user.getUser); router.post("/auth/login", user.login);
router.post("/auth/signup", user.signup);
// router.get("/user/:id", user.getUser);
router.put("/user", user.putUser); router.put("/user", user.putUser);
router.delete("/user/:id", user.deleteUser); // router.delete("/user/:id", user.deleteUser);
router.get("/budget/:id", budget.getBudget); router.get("/shared_note/:limit", note.getSharedNotes);
router.put("/budget", budget.putBudget); router.put("/shared_note", note.putSharedNote);
router.post("/budget", budget.postBudget); router.post("/shared_note", note.postSharedNote);
// router.get("/budget/:id", budget.getBudget);
// router.put("/budget", budget.putBudget);
// router.post("/budget", budget.postBudget);
router.put("/budget_category", budget.putBudgetCategory);
router.post("/budget_category", budget.postBudgetCategory); router.post("/budget_category", budget.postBudgetCategory);
router.put("/budget_category", budget.putBudgetCategory);
router.delete("/budget_category", budget.deleteBudgetCategory);
router.get("/transactions/:budget_id", trans.getTransactions); router.post("/transaction", trans.postTransaction);
router.post("/transactions", trans.postTransaction); router.put("/transaction", trans.putTransaction);
router.put("/transactions", trans.putTransaction); router.delete("/transaction", trans.deleteTransaction);
router.get("/dashboard/:family_id", dash.getDashboard); router.get("/dashboard", dash.getDashboard);
std.debug.print("Starting http server listening on port {}\n", .{8081}); std.debug.print("Starting http server listening on port {}\n", .{8081});
// start the server in the current thread, blocking. // start the server in the current thread, blocking.
@ -63,7 +102,10 @@ fn notFound(_: *httpz.Request, res: *httpz.Response) !void {
// you can set the body directly to a []u8, but note that the memory // you can set the body directly to a []u8, but note that the memory
// must be valid beyond your handler. Use the res.arena if you need to allocate // must be valid beyond your handler. Use the res.arena if you need to allocate
// memory for the body. // memory for the body.
res.body = "Not Found"; try res.json(
.{ .success = false, .message = "Not Found" },
.{},
);
} }
// note that the error handler return `void` and not `!void` // note that the error handler return `void` and not `!void`
@ -75,14 +117,15 @@ fn errorHandler(req: *httpz.Request, res: *httpz.Response, err: anyerror) void {
pub fn returnError(message: ?[]const u8, comptime statusCode: u16, res: *httpz.Response) void { pub fn returnError(message: ?[]const u8, comptime statusCode: u16, res: *httpz.Response) void {
comptime { comptime {
if (statusCode < 300 or statusCode > 500) { if (statusCode > 500 or statusCode < 200) {
@compileError("Failed responses must have status codes between 300 and 500"); @compileError("Failed responses must have status codes between 200 and 500");
} }
} }
res.status = statusCode; res.status = statusCode;
res.json(.{ .success = false, .message = message }, .{}) catch |err| { std.log.info("Returning error", .{});
return res.json(.{ .success = false, .message = message }, .{}) catch |err| {
std.log.warn("Couldnt create error body: {}", .{err}); std.log.warn("Couldnt create error body: {}", .{err});
res.body = "{ \"success\": false"; res.body = "{ \"success\": false }";
}; };
} }
@ -91,3 +134,23 @@ pub fn returnData(data: anytype, res: *httpz.Response) !void {
res.status = 200; res.status = 200;
try res.json(body, .{}); try res.json(body, .{});
} }
const ReqDataError = error{
MalformedBody,
NoData,
};
pub fn getReqJson(req: *httpz.Request, res: *httpz.Response, comptime ReqType: type) ReqDataError!ReqType {
const body_data = req.json(ReqType) catch |err| {
std.debug.print("Malformed body: {any}\nExpected {any} for req {s} on {s}", .{ err, ReqType, @tagName(req.method), req.url.query });
returnError("Bad request: Malformed Body", 400, res);
return ReqDataError.MalformedBody;
};
if (body_data == null) {
std.debug.print("Bad request, no data\nExpected {any} for req {s} on {s}", .{ ReqType, @tagName(req.method), req.url.query });
returnError("Bad request: No Data", 400, res);
return ReqDataError.NoData;
}
var body = body_data.?;
return body;
}

View File

@ -5,10 +5,8 @@ const http = @import("./http_handler.zig");
pub fn main() !void { pub fn main() !void {
std.debug.print("\nStarting Server...\n", .{}); std.debug.print("\nStarting Server...\n", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); // const allocator = gpa.allocator();
var db = try Db.init(allocator, null); // _ = allocator;
defer db.deinit();
// try db.wipeAndMigrateDb();
try http.startHttpServer(); try http.startHttpServer();
} }

View File

@ -0,0 +1,89 @@
const std = @import("std");
const jwt = @import("../.deps/jwt.zig");
const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig");
const handler = @import("../http_handler.zig");
const utils = @import("../utils.zig");
//TODO move these to env variables
const key = "aGVyZUlzQUdpYmVyaXNoS2V5ISE=";
pub const VerifyAuthError = error{
Unauthorized,
NotAuthenticated,
BadToken,
Expired,
};
pub fn verifyRequest(req: *httpz.Request, res: *httpz.Response, user_id: ?u32, family_id: ?u32) !models.Token {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const coded_token = req.headers.get("token");
const now =
@bitCast(u64, std.time.milliTimestamp());
const date = ztime.DateTime.now();
const formatted_now = date.formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";
const method = @tagName(req.method);
if (coded_token == null) {
handler.returnError("Unauthorized/NoToken", 401, res);
std.log.info("{s} {s} Unauthorized/NotAuthenticated - @ {s}", .{ method, req.url.query, formatted_now });
return VerifyAuthError.NotAuthenticated;
}
const token = jwt.validate(models.Token, allocator, .HS256, coded_token.?, .{ .key = key }) catch {
handler.returnError("Unauthorized", 400, res);
std.log.info("{s} {s} Unauthorized/BadToken - Token: {s} @ {s}", .{ method, req.url.raw, coded_token.?, formatted_now });
return VerifyAuthError.BadToken;
};
if (user_id != null and user_id.? != token.user_id or family_id != null and family_id.? != token.family_id) {
handler.returnError("Unauthorized", 401, res);
std.log.info("{s} {s} Unauthorized/BadCredentials - User: {} Family: {any} @ {s}, Tried User: {any} Family: {any}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now, user_id, family_id });
return VerifyAuthError.Unauthorized;
}
if (token.expires_at < now) {
std.log.info("{s} {s} Unauthorized/Expired - User: {} Family: {any} @ {s}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now });
handler.returnError("Credentials Expired", 403, res);
return VerifyAuthError.Expired;
}
std.log.info("{s} {s} Authorized - User: {} Family: {any} @ {s}", .{ method, req.url.raw, token.user_id, token.family_id, formatted_now });
return token;
}
pub fn generateToken(user: models.User) ![]const u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const now = @bitCast(u64, std.time.milliTimestamp());
var seven_days = ztime.DateTime.now();
seven_days = seven_days.addDays(7);
const token: models.Token = .{ .user_id = user.id, .family_id = user.family_id, .generated_at = now, .expires_at = seven_days.toUnixMilli() };
const encoded_token = try jwt.encode(allocator, .HS256, token, .{ .key = key });
// std.log.info("Generated token for User {} @ {}", .{ user.id, now });
return encoded_token;
}
test {
var gpa = std.testing.allocator_instance;
const allocator = gpa.allocator();
const user = models.User{ .id = 1, .family_id = 1, .budget_id = 1, .name = "Billy", .created_at = 1, .updated_at = 1, .last_activity_at = 1, .hide = 0 };
const user_token = try generateToken(user);
// std.debug.print("user_token: {s}\n", .{user_token});
// var key = try std.testing.allocator.alloc(u8, try base64url.Decoder.calcSizeForSlice(key_base64));
// defer std.testing.allocator.free(key);
// try base64url.Decoder.decode(key, key_base64);
// std.debug.print("key: {s}\n", .{key});
// const user_token: models.Token = .{ .user_id = 1, .family_id = 1, .generated_at = @bitCast(u64, std.time.milliTimestamp()), .expires_at = @bitCast(u64, std.time.milliTimestamp()) };
// const coded_message = try jwt.encode(allocator, .HS256, user_token, .{ .key = key });
const decoded_message = try jwt.validate(models.Token, allocator, .HS256, user_token, .{ .key = key });
std.debug.print("user: {any}\nuser_token: {s}\ndecoded: {any}\n", .{ user, user_token, decoded_message });
}

View File

@ -3,122 +3,123 @@ const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig"); const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig"); const ztime = @import("../.deps/time.zig");
const utils = @import("../utils.zig"); const utils = @import("../utils.zig");
const auth = @import("auth.zig");
const handler = @import("../http_handler.zig"); const handler = @import("../http_handler.zig");
pub fn getBudget(req: *httpz.Request, res: *httpz.Response) !void { // pub fn getBudget(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb(); // const db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); // const allocator = gpa.allocator();
const id_str = req.param("id"); // const id_str = req.param("id");
if (id_str == null) { // if (id_str == null) {
res.status = 400; // res.status = 400;
res.body = "Bad Request: No Id"; // res.body = "Bad Request: No Id";
return; // return;
} // }
const id = std.fmt.parseInt(u32, id_str.?, 0) catch { // const id = std.fmt.parseInt(u32, id_str.?, 0) catch {
res.status = 401; // res.status = 401;
res.body = "Bad Request: Bad Id"; // res.body = "Bad Request: Bad Id";
return; // return;
}; // };
const budget = try db.selectOneById(models.Budget, allocator, id); // const budget = try db.selectOneById(models.Budget, allocator, id);
if (budget == null) { // if (budget == null) {
res.status = 404; // res.status = 404;
res.body = "Budget not found"; // res.body = "Budget not found";
return; // return;
} // }
try res.json(budget.?, .{}); // try res.json(budget.?, .{});
} // }
const BudgetPostReq = struct { // const BudgetPostReq = struct {
id: ?u32, // id: ?u32,
name: []const u8, // family_id: u32,
created_at: ?u64, // name: []const u8,
updated_at: ?u64, // created_at: ?u64,
hide: u8, // updated_at: ?u64,
}; // hide: u8,
// };
pub fn putBudget(req: *httpz.Request, res: *httpz.Response) !void { // pub fn putBudget(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb(); // var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); // const allocator = gpa.allocator();
const body_data = req.json(models.Budget) catch |err| { // const body_data = req.json(models.Budget) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err}); // std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad Request: Malformed Body", 400, res); // handler.returnError("Bad Request: Malformed Body", 400, res);
return; // return;
}; // };
if (body_data == null) { // if (body_data == null) {
handler.returnError("Bad Request: No Data", 400, res); // handler.returnError("Bad Request: No Data", 400, res);
return; // return;
} // }
var body = body_data.?; // var body = body_data.?;
// Add Budget // // Add Budget
const now = @intCast(u64, std.time.milliTimestamp()); // const now = @intCast(u64, std.time.milliTimestamp());
// Update existing Budget // // Update existing Budget
body.updated_at = now; // body.updated_at = now;
try db.updateById(models.Budget, body); // try db.updateById(models.Budget, body);
const query = models.createSelectOnIdQuery(models.Transaction); // const query = models.createSelectOnIdQuery(models.Transaction);
const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .id = body.id }); // const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .id = body.id });
if (updated_budget) |budget| { // if (updated_budget) |budget| {
try handler.returnData(budget, res); // try handler.returnData(budget, res);
} else { // } else {
handler.returnError("Internal Server Error", 500, res); // handler.returnError("Internal Server Error", 500, res);
} // }
return; // return;
} // }
pub fn postBudget(req: *httpz.Request, res: *httpz.Response) !void { // pub fn postBudget(req: *httpz.Request, res: *httpz.Response) !void {
comptime { // comptime {
const putReqLen = @typeInfo(BudgetPostReq).Struct.fields.len; // const putReqLen = @typeInfo(BudgetPostReq).Struct.fields.len;
const budgetLen = @typeInfo(models.Budget).Struct.fields.len; // const budgetLen = @typeInfo(models.Budget).Struct.fields.len;
if (putReqLen != budgetLen) { // if (putReqLen != budgetLen) {
@compileError(std.fmt.comptimePrint("BudgetPutReq does not equal Budget model struct, fields inconsistent", .{})); // @compileError(std.fmt.comptimePrint("BudgetPostReq does not equal Budget model struct, fields inconsistent", .{}));
} // }
} // }
var db = handler.getDb(); // var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); // const allocator = gpa.allocator();
const body_data = req.json(BudgetPostReq) catch |err| { // const body_data = req.json(BudgetPostReq) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err}); // std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad request: Malformed Body", 400, res); // handler.returnError("Bad request: Malformed Body", 400, res);
return; // return;
}; // };
if (body_data == null) { // if (body_data == null) {
handler.returnError("Bad request: No Data", 400, res); // handler.returnError("Bad request: No Data", 400, res);
return; // return;
} // }
var body = body_data.?; // var body = body_data.?;
if (body.id != null) { // if (body.id != null) {
handler.returnError("Bad Request: ID", 400, res); // handler.returnError("Bad Request: ID", 400, res);
} // }
// Add Budget // // Add Budget
const now = @intCast(u64, std.time.milliTimestamp()); // const now = @intCast(u64, std.time.milliTimestamp());
// Create Budget // // Create Budget
body.created_at = now; // body.created_at = now;
body.updated_at = now; // body.updated_at = now;
try db.insert(models.Budget, utils.removeStructFields(body, &[_]u8{0})); // try db.insert(models.Budget, utils.removeStructFields(body, &[_]u8{0}));
// Get Budget // // Get Budget
const query = try models.createSelectOnFieldQuery(models.Budget, null, "created_at", "="); // const query = try models.createSelectOnFieldQuery(models.Budget, null, "created_at", "=");
const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .created_at = body.created_at }); // const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .created_at = body.created_at });
if (updated_budget) |budget| { // if (updated_budget) |budget| {
try handler.returnData(budget, res); // try handler.returnData(budget, res);
} else { // } else {
handler.returnError("Internal Server Error", 500, res); // handler.returnError("Internal Server Error", 500, res);
} // }
return; // return;
} // }
const BudgetCatPostReq = struct { const BudgetCatPostReq = struct {
id: ?u32, id: ?u32,
@ -160,6 +161,16 @@ pub fn postBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
handler.returnError("Bad request: ID", 400, res); handler.returnError("Bad request: ID", 400, res);
return; return;
} }
const budget = try db.selectOneById(models.Budget, allocator, body.budget_id);
if (budget == null) {
handler.returnError("No budget found", 404, res);
}
_ = auth.verifyRequest(req, res, null, budget.?.family_id) catch {
return;
};
// Add Budget // Add Budget
const now = @intCast(u64, std.time.milliTimestamp()); const now = @intCast(u64, std.time.milliTimestamp());
// Create Budget // Create Budget
@ -170,15 +181,15 @@ pub fn postBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
// Get Budget // Get Budget
const query = try models.createSelectOnFieldQuery(models.BudgetCategory, null, "created_at", "="); const query = try models.createSelectOnFieldQuery(models.BudgetCategory, null, "created_at", "=");
const updated_budget = try db.selectOne(models.BudgetCategory, allocator, query, .{ .created_at = body.created_at }); const updated_budget_cat = try db.selectOne(models.BudgetCategory, allocator, query, .{ .created_at = body.created_at });
if (updated_budget) |budget| {
try handler.returnData(budget, res); if (updated_budget_cat == null) {
} else {
std.debug.print("Could not find inserted budget", .{}); std.debug.print("Could not find inserted budget", .{});
handler.returnError("Internal Server Error", 500, res); handler.returnError("Internal Server Error", 500, res);
}
return; return;
} }
try handler.returnData(updated_budget_cat.?, res);
}
pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void { pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb(); var db = handler.getDb();
@ -196,6 +207,15 @@ pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
} }
var budget_category = body_data.?; var budget_category = body_data.?;
const budget = try db.selectOneById(models.Budget, allocator, budget_category.budget_id);
if (budget == null) {
handler.returnError("No budget found", 404, res);
}
_ = auth.verifyRequest(req, res, null, budget.?.family_id) catch {
return;
};
const now = @intCast(u64, std.time.milliTimestamp()); const now = @intCast(u64, std.time.milliTimestamp());
// Update existing Budget // Update existing Budget
@ -203,12 +223,47 @@ pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
try db.updateById(models.BudgetCategory, budget_category); try db.updateById(models.BudgetCategory, budget_category);
const query = models.createSelectOnIdQuery(models.BudgetCategory); const query = models.createSelectOnIdQuery(models.BudgetCategory);
const updated_budget = try db.selectOne(models.BudgetCategory, allocator, query, .{ .id = budget_category.id }); const updated_budget_cat = try db.selectOne(models.BudgetCategory, allocator, query, .{ .id = budget_category.id });
if (updated_budget) |budget| { if (updated_budget_cat == null) {
try handler.returnData(budget, res);
} else {
std.debug.print("Could not find inserted budget", .{}); std.debug.print("Could not find inserted budget", .{});
handler.returnError("Internal Server Error", 500, res); handler.returnError("Internal Server Error", 500, res);
}
return; return;
} }
try handler.returnData(updated_budget_cat.?, res);
return;
}
const deleteIdReq = struct {
id: u32,
};
pub fn deleteBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body = handler.getReqJson(req, res, deleteIdReq) catch {
return;
};
const budget_category = try db.selectOneById(models.BudgetCategory, allocator, body.id);
if (budget_category == null) {
return handler.returnError("Cannot find budget", 404, res);
}
const budget = try db.selectOneById(models.Budget, allocator, budget_category.?.budget_id);
if (budget == null) {
return handler.returnError("Cannot find budget", 404, res);
}
_ = auth.verifyRequest(req, res, null, budget.?.family_id) catch {
return;
};
try db.updateHideById(models.BudgetCategory, true, body.id);
const updated_budget_category = try db.selectOneById(models.BudgetCategory, allocator, body.id);
if (budget_category == null) {
return handler.returnError("Could not delete category", 500, res);
}
return try handler.returnData(updated_budget_category.?, res);
}

View File

@ -4,7 +4,9 @@ const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig"); const ztime = @import("../.deps/time.zig");
const utils = @import("../utils.zig"); const utils = @import("../utils.zig");
const trans = @import("transactions.zig"); const trans = @import("transactions.zig");
const note = @import("shared_note.zig");
const handler = @import("../http_handler.zig"); const handler = @import("../http_handler.zig");
const auth = @import("auth.zig");
pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void { pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb(); const db = handler.getDb();
@ -12,41 +14,62 @@ pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const family_id_str = req.param("family_id"); const token = auth.verifyRequest(req, res, null, null) catch {
if (family_id_str == null) {
res.status = 400;
res.body = "Bad Request: No FamilyId";
return;
}
const family_id = std.fmt.parseInt(u32, family_id_str.?, 0) catch {
res.status = 400;
res.body = "Bad Request: Bad FamilyId";
return; return;
}; };
const family = db.selectOneById(models.Family, allocator, family_id) catch |err| { // const family_id = std.fmt.parseInt(u32, family_id_str.?, 0) catch {
if (err == error.SQLiteError) { // res.status = 400;
res.status = 404; // res.body = "Bad Request: Bad FamilyId";
res.body = "Family Not Found"; // return;
return; // };
}
std.debug.print("Error while getting family: {}\n", .{err}); const family = db.selectOneById(models.Family, allocator, token.family_id) catch |err| {
res.status = 500; std.log.err("Error getting family in dashboard: {any}", .{err});
res.body = "Internal Server Error"; return handler.returnError("Unexpected Server Error", 500, res);
return;
}; };
if (family == null) { if (family == null) {
res.status = 404; std.log.err("Family not found, invalidating client token", .{});
res.body = "Family Not Found"; return handler.returnError("Family does not exist or forbidden", 403, res);
return;
} }
const transactions = try trans.fetchTransFromDb(allocator, family.?.budget_id); const user = db.selectOneById(models.User, allocator, token.user_id) catch |err| {
std.log.err("User not found, invalidating client token, Err: {any}", .{err});
return handler.returnError("Could not get user or forbidden", 403, res);
};
if (user == null) {
std.log.err("User not found, invalidating client token", .{});
return handler.returnError("User not found or forbidden", 403, res);
}
const transactions = trans.fetchTransFromDb(allocator, family.?.id) catch |err| {
std.log.err("Unexpected error while getting transactions: {any}", .{err});
handler.returnError("Internal Server Error", 500, res);
return;
};
const notes = note.fetchNotesFromDb(allocator, family.?.id) catch |err| {
std.log.err("Unexpected error while getting transactions: {any}", .{err});
handler.returnError("Internal Server Error", 500, res);
return;
};
const budget_query = try models.createSelectOnFieldQuery(models.Budget, null, "family_id", "=");
const budget = db.selectOne(models.Budget, allocator, budget_query, .{ .family_id = token.family_id }) catch |err| {
std.log.err("Unexpected error while getting budget: {any}", .{err});
handler.returnError("Internal Server Error", 500, res);
return;
};
const budget = try db.selectOneById(models.Budget, allocator, family.?.budget_id);
var budget_categories: ?[]models.BudgetCategory = null; var budget_categories: ?[]models.BudgetCategory = null;
if (budget != null) { if (budget != null) {
budget_categories = try db.selectAllWhere(models.BudgetCategory, allocator, "WHERE budget_id = ? AND hide = ?", .{ .budget_id = budget.?.id, .hide = 0 }, null); budget_categories = db.selectAllWhere(models.BudgetCategory, allocator, "WHERE budget_id = ? AND hide = ?", .{ .budget_id = budget.?.id, .hide = 0 }, null, null, null, null) catch |err| {
std.log.err("Unexpected error while getting budget categories: {any}", .{err});
handler.returnError("Internal Server Error", 500, res);
return;
};
} }
if (budget_categories == null) { if (budget_categories == null) {
@ -54,9 +77,12 @@ pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
} }
const response_body = .{ const response_body = .{
.family = family.?, .family = family.?,
.user = user.?,
.budget = budget, .budget = budget,
.budget_categories = budget_categories, .budget_categories = budget_categories,
.transactions = transactions, .transactions = transactions,
.shared_notes = notes,
.success = true,
}; };
try res.json(response_body, .{}); try res.json(response_body, .{});
} }

155
src/routes/shared_note.zig Normal file
View File

@ -0,0 +1,155 @@
const std = @import("std");
const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig");
// const ztime = @import("../.deps/time.zig");
const utils = @import("../utils.zig");
const auth = @import("auth.zig");
const handler = @import("../http_handler.zig");
pub fn fetchNotesFromDb(allocator: std.mem.Allocator, family_id: u32) !?[]models.SharedNote {
var db = handler.getDb();
const notes = db.selectAllWhere(
models.SharedNote,
allocator,
"WHERE family_id = ? and hide = ?",
.{ .family_id = family_id, .hide = 0 },
"updated_at",
"DESC",
true,
10,
) catch |err| {
std.debug.print("Error while getting shared notes: {any}", .{err});
return err;
};
return notes;
}
pub fn getSharedNotes(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var str_limit: ?[]const u8 = req.param("limit");
if (str_limit == null) {
str_limit = "10";
}
const limit = std.fmt.parseInt(u32, str_limit.?, 0) catch {
handler.returnError("Bad Request: Bad Limit", 401, res);
return;
};
const token = auth.verifyRequest(req, res, null, null) catch {
return;
};
const notes = db.selectAllWhere(
models.SharedNote,
allocator,
"WHERE family_id = ? and hide = ?",
.{ .family_id = token.family_id, .hide = 0 },
"updated_at",
"DESC",
true,
limit,
) catch |err| {
std.debug.print("Error while getting shared notes: {any}", .{err});
return err;
};
try res.json(.{ .notes = notes }, .{});
return;
}
pub fn putSharedNote(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body_data = req.json(models.SharedNote) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad request: Malformed Body", 400, res);
return;
};
if (body_data == null) {
handler.returnError("Bad request: No Data", 400, res);
return;
}
var shared_note = body_data.?;
_ = auth.verifyRequest(req, res, shared_note.created_by_user_id, shared_note.family_id) catch {
return;
};
const now = @intCast(u64, std.time.milliTimestamp());
shared_note.updated_at = now;
try db.updateById(models.SharedNote, shared_note);
const updated_note = try db.selectOneById(models.SharedNote, allocator, shared_note.id);
if (updated_note) |note| {
try handler.returnData(note, res);
} else {
handler.returnError("Internal Server Error", 500, res);
}
return;
}
const NotePostReq = struct {
id: ?u32,
family_id: u32,
created_by_user_id: u32,
content: []const u8,
title: []const u8,
color: ?[]const u8,
tag_ids: []const u8,
is_markdown: u2,
created_at: ?u64,
updated_at: ?u64,
hide: u8,
};
pub fn postSharedNote(req: *httpz.Request, res: *httpz.Response) !void {
comptime {
const postReqLen = @typeInfo(NotePostReq).Struct.fields.len;
const noteLen = @typeInfo(models.SharedNote).Struct.fields.len;
if (postReqLen != noteLen) {
@compileError(std.fmt.comptimePrint("SharedNotePutReq does not equal SharedNote model struct, fields inconsistent", .{}));
}
}
var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body_data = req.json(NotePostReq) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad request: Malformed Body", 400, res);
return;
};
if (body_data == null) {
handler.returnError("Bad request: No Data", 400, res);
return;
}
var body = body_data.?;
_ = auth.verifyRequest(req, res, body.created_by_user_id, body.family_id) catch {
return;
};
const now = @intCast(u64, std.time.milliTimestamp());
body.created_at = now;
body.updated_at = now;
// remove the null id field for insertion
try db.insert(models.SharedNote, utils.removeStructFields(body, &[_]u8{0}));
// Get new SharedNote
const query = try models.createSelectOnFieldQuery(models.SharedNote, null, "created_at", "=");
const updated_note = try db.selectOne(models.SharedNote, allocator, query, .{ .created_at = body.created_at });
if (updated_note) |note| {
try handler.returnData(note, res);
} else {
handler.returnError("Internal Server Error", 500, res);
}
return;
}

View File

@ -3,56 +3,84 @@ const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig"); const models = @import("../db/models.zig");
const ztime = @import("../.deps/time.zig"); const ztime = @import("../.deps/time.zig");
const utils = @import("../utils.zig"); const utils = @import("../utils.zig");
const time = @import("../.deps/datetime.zig");
const tz = @import("../.deps/timezones.zig");
const auth = @import("auth.zig");
const handler = @import("../http_handler.zig"); const handler = @import("../http_handler.zig");
pub fn fetchTransFromDb(allocator: std.mem.Allocator, budget_id: u32) !?[]models.Transaction { pub fn fetchTransFromDb(allocator: std.mem.Allocator, family_id: u32) !?[]models.Transaction {
var db = handler.getDb(); var db = handler.getDb();
const now = ztime.DateTime.now(); const now = time.Datetime.now();
const beginningOfMonth = ztime.DateTime.init(now.years, now.months, 0, 0, 0, 0); var beginning_of_month = try time.Datetime.fromDate(now.date.year, now.date.month, 1);
// const timezone_begin = beginning_of_month.shiftTimezone(&tz.US.Mountain);
const timezone_begin = beginning_of_month.shiftHours(6);
// std.log.info("Beginning: {} Shifted: {}", .{ beginning_of_month.date.toTimestamp(), timezone_begin.date.toTimestamp() });
// const unix = @bitCast(u64, beginning_of_month.date.toTimestamp() + beginning_of_month.time.toTimestamp());
const begin_time = @bitCast(u64, timezone_begin.date.toTimestamp() + timezone_begin.time.toTimestamp());
// std.log.info("Fetching transactions after beginning of month: unix {}, adjusted {}", .{ unix, begin_time });
comptime { comptime {
if (!std.mem.eql(u8, @typeInfo(models.Transaction).Struct.fields[7].name, "date")) { if (!std.mem.eql(u8, @typeInfo(models.Transaction).Struct.fields[7].name, "date")) {
return error{TransactionModelError}; return error{TransactionModelError};
} }
} }
const transactions = try db.selectAllWhere( const budget_query = models.createSelectOnFieldQuery(models.Budget, null, "family_id", "=") catch |err| {
std.debug.print("Error while creating budget query: {any}", .{err});
return err;
};
const budget = db.selectOne(models.Budget, allocator, budget_query, .{ .family_id = family_id }) catch |err| {
std.debug.print("Error while getting budget: {any}", .{err});
return err;
};
if (budget == null) {
return null;
}
const transactions = db.selectAllWhere(
models.Transaction, models.Transaction,
allocator, allocator,
"WHERE budget_id = ? AND date > ? AND hide = ?", "WHERE budget_id = ? AND date > ? AND hide = ?",
.{ .budget_id = budget_id, .date = beginningOfMonth.toUnixMilli(), .hide = 0 }, .{ .budget_id = budget.?.id, .date = begin_time, .hide = 0 },
null, null,
); null,
null,
null,
) catch |err| {
std.debug.print("Error while getting transactions query: {any}", .{err});
return err;
};
return transactions; return transactions;
} }
pub fn getTransactions(req: *httpz.Request, res: *httpz.Response) !void { // pub fn getTransactions(req: *httpz.Request, res: *httpz.Response) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // auth.verifyRequest();
const allocator = gpa.allocator(); // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// const allocator = gpa.allocator();
const budget_id_str = req.param("budget_id"); // const budget_id_str = req.param("budget_id");
if (budget_id_str == null) { // if (budget_id_str == null) {
handler.returnError("Bad request", 400, res); // handler.returnError("Bad request", 400, res);
return; // return;
} // }
const budget_id = std.fmt.parseInt(u32, budget_id_str.?, 0) catch { // const budget_id = std.fmt.parseInt(u32, budget_id_str.?, 0) catch {
handler.returnError("Bad request", 400, res); // handler.returnError("Bad request", 400, res);
return; // return;
}; // };
const transactions = try fetchTransFromDb(allocator, budget_id); // const transactions = try fetchTransFromDb(allocator, budget_id);
if (transactions == null) { // if (transactions == null) {
res.status = 200; // res.status = 200;
res.body = ""; // res.body = "";
return; // return;
} // }
// std.debug.print("Transactions got:\n", .{}); // // std.debug.print("Transactions got:\n", .{});
// for (transactions.?) |transaction| { // // for (transactions.?) |transaction| {
// std.debug.print("\t{any}\n", .{transaction}); // // std.debug.print("\t{any}\n", .{transaction});
// // }
// try handler.returnData(.{ .transactions = transactions.? }, res);
// } // }
try handler.returnData(.{ .transactions = transactions.? }, res);
}
pub fn putTransaction(req: *httpz.Request, res: *httpz.Response) !void { pub fn putTransaction(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb(); var db = handler.getDb();
@ -70,9 +98,16 @@ pub fn putTransaction(req: *httpz.Request, res: *httpz.Response) !void {
} }
var transaction = body_data.?; var transaction = body_data.?;
// Add Transaction const budget = try db.selectOneById(models.Budget, allocator, transaction.budget_id);
if (budget == null) {
return handler.returnError("Budget for transaction not found or forbidden", 403, res);
}
_ = auth.verifyRequest(req, res, transaction.created_by_user_id, budget.?.family_id) catch {
return;
};
const now = @intCast(u64, std.time.milliTimestamp()); const now = @intCast(u64, std.time.milliTimestamp());
// Update existing Transaction
transaction.updated_at = now; transaction.updated_at = now;
try db.updateById(models.Transaction, transaction); try db.updateById(models.Transaction, transaction);
@ -92,7 +127,7 @@ const TransPostReq = struct {
type: []const u8, type: []const u8,
memo: ?[]const u8, memo: ?[]const u8,
budget_id: u32, budget_id: u32,
added_by_user_id: u32, created_by_user_id: u32,
budget_category_id: ?u32, budget_category_id: ?u32,
date: u64, date: u64,
created_at: ?u64, created_at: ?u64,
@ -102,9 +137,9 @@ const TransPostReq = struct {
pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void { pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
comptime { comptime {
const putReqLen = @typeInfo(TransPostReq).Struct.fields.len; const postReqLen = @typeInfo(TransPostReq).Struct.fields.len;
const transLen = @typeInfo(models.Transaction).Struct.fields.len; const transLen = @typeInfo(models.Transaction).Struct.fields.len;
if (putReqLen != transLen) { if (postReqLen != transLen) {
@compileError(std.fmt.comptimePrint("TransactionPutReq does not equal Transaction model struct, fields inconsistent", .{})); @compileError(std.fmt.comptimePrint("TransactionPutReq does not equal Transaction model struct, fields inconsistent", .{}));
} }
} }
@ -112,26 +147,23 @@ pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const body_data = req.json(TransPostReq) catch |err| { var body = handler.getReqJson(req, res, TransPostReq) catch {
std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad request: Malformed Body", 400, res);
return; return;
}; };
if (body_data == null) {
handler.returnError("Bad request: No Data", 400, res);
return;
}
var body = body_data.?;
if (body.id != null) { const budget = try db.selectOneById(models.Budget, allocator, body.budget_id);
handler.returnError("Bad request: ID", 400, res); if (budget == null) {
return; return handler.returnError("Budget for transaction not foud or forbidden", 403, res);
} }
_ = auth.verifyRequest(req, res, body.created_by_user_id, budget.?.family_id) catch {
return;
};
const now = @intCast(u64, std.time.milliTimestamp()); const now = @intCast(u64, std.time.milliTimestamp());
body.created_at = now; body.created_at = now;
body.updated_at = now; body.updated_at = now;
// remove the null id field for insertion
try db.insert(models.Transaction, utils.removeStructFields(body, &[_]u8{0})); try db.insert(models.Transaction, utils.removeStructFields(body, &[_]u8{0}));
// Get new Transaction // Get new Transaction
@ -144,3 +176,38 @@ pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
} }
return; return;
} }
const deleteIdReq = struct {
id: u32,
};
pub fn deleteTransaction(req: *httpz.Request, res: *httpz.Response) !void {
var db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body = handler.getReqJson(req, res, deleteIdReq) catch {
return;
};
const transaction = try db.selectOneById(models.Transaction, allocator, body.id);
if (transaction == null) {
return handler.returnError("Id invalid, not a transaction", 400, res);
}
const budget = try db.selectOneById(models.Budget, allocator, transaction.?.budget_id);
if (budget == null) {
return handler.returnError("Cannot find associated budget", 404, res);
}
_ = auth.verifyRequest(req, res, null, budget.?.family_id) catch {
return;
};
try db.updateHideById(models.Transaction, true, body.id);
const updated_transaction = try db.selectOneById(models.Transaction, allocator, body.id);
if (updated_transaction == null) {
return handler.returnError("Could not delete transaction", 500, res);
}
return try handler.returnData(updated_transaction.?, res);
}

View File

@ -2,8 +2,208 @@ const std = @import("std");
const httpz = @import("../.deps/http.zig/src/httpz.zig"); const httpz = @import("../.deps/http.zig/src/httpz.zig");
const models = @import("../db/models.zig"); const models = @import("../db/models.zig");
const utils = @import("../utils.zig"); const utils = @import("../utils.zig");
const auth = @import("auth.zig");
const handler = @import("../http_handler.zig"); const handler = @import("../http_handler.zig");
const ztime = @import("../.deps/time.zig");
const LoginReq = struct {
username: ?[]const u8,
email: ?[]const u8,
password: []const u8,
};
// POST endpoint for user login requests
pub fn login(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body_data = req.json(LoginReq) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad Request: Malformed Body", 400, res);
return;
};
if (body_data == null) {
handler.returnError("Bad Request: No Data", 400, res);
return;
}
const formatted_now = ztime.DateTime.now().formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";
std.log.info("{s} {s} @ {s}\n", .{ @tagName(req.method), req.url.raw, formatted_now });
var body = body_data.?;
if (body.username == null and body.email == null) {
handler.returnError("Bad Request: Missing username / email", 400, res);
return;
} else if (body.username != null and body.email != null) {
handler.returnError("Bad Request: Provided Username and Email", 400, res);
return;
}
var user: ?models.User = null;
const password_hash = try utils.hashPassword(allocator, body.password);
if (body.username != null) {
const query =
"WHERE pass_hash = ? and username = ?;";
user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
models.User,
query,
), .{ .pass_hash = password_hash, .username = body.username.? });
} else {
const query =
"WHERE pass_hash = ? and email = ?;";
user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
models.User,
query,
), .{ .pass_hash = password_hash, .email = body.email.? });
}
if (user == null) {
handler.returnError("User not found or incorrect password", 200, res);
return;
} else if (user.?.hide == 1) {
handler.returnError("Account has been closed", 200, res);
return;
}
const token = try auth.generateToken(user.?);
try handler.returnData(.{ .token = token }, res);
}
const SignupReq = struct {
name: []const u8,
username: []const u8,
email: ?[]const u8,
password: []const u8,
family_code: ?[]const u8,
budget_name: []const u8,
};
// POST endpoint for user signups
pub fn signup(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const body_data = req.json(SignupReq) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad Request: Malformed Body", 400, res);
return;
};
if (body_data == null) {
handler.returnError("Bad Request: No Data", 400, res);
return;
}
const formatted_now = ztime.DateTime.now().formatAlloc(allocator, "DD.MM.YYYY HH:mm:ss") catch "N/A";
std.log.info("{s} {s} @ {s}\n", .{ @tagName(req.method), req.url.raw, formatted_now });
var body = body_data.?;
// if (body.username == null and body.email == null) {
// handler.returnError("Bad Request: Missing username / email", 400, res);
// return;
// }
const password_hash = try utils.hashPassword(allocator, body.password);
const now = @bitCast(u64, std.time.milliTimestamp());
const uname_query =
"WHERE username = ?;";
var temp_user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
models.User,
uname_query,
), .{ .username = body.username });
if (temp_user != null) {
handler.returnError("Username is unavailable", 200, res);
return;
}
if (body.email != null) {
const email_query =
"WHERE email = ?;";
temp_user = try db.selectOne(models.User, allocator, try models.createRawSelectQuery(
models.User,
email_query,
), .{ .email = body.email.? });
if (temp_user != null) {
handler.returnError("Email is unavailable", 200, res);
return;
}
}
var family: ?models.Family = null;
var budget: ?models.Budget = null;
var user: ?models.User = null;
if (body.family_code == null) {
const new_family = .{
.code = utils.generateRandomString(allocator) catch null,
.created_at = now,
.updated_at = now,
.hide = 0,
};
try db.insert(models.Family, new_family);
const family_query = try models.createSelectOnFieldQuery(models.Family, null, "code", "=");
family = try db.selectOne(models.Family, allocator, family_query, .{ .code = new_family.code });
if (family == null) {
handler.returnError("Unable to create family", 500, res);
return;
}
const new_budget = .{
.family_id = family.?.id,
.name = body.budget_name,
.created_at = now,
.updated_at = now,
.hide = 0,
.expected_income = null,
};
try db.insert(models.Budget, new_budget);
const budget_query = try models.createSelectOnFieldQuery(models.Budget, null, "created_at", "=");
budget = try db.selectOne(models.Budget, allocator, budget_query, .{ .created_at = now });
if (budget == null) {
handler.returnError("Unable to create budget", 500, res);
return;
}
} else {
const family_query = try models.createSelectOnFieldQuery(models.Family, null, "code", "=");
family = try db.selectOne(models.Family, allocator, family_query, .{ .code = body.family_code.? });
if (family == null) {
return handler.returnError("Invalid Family Code", 404, res);
}
const budget_query = try models.createSelectOnFieldQuery(models.Budget, null, "family_id", "=");
budget = try db.selectOne(models.Budget, allocator, budget_query, .{ .family_id = family.?.id });
if (budget == null) {
return handler.returnError("Could not find Family Budget", 404, res);
}
}
const new_user = .{ .name = body.name, .username = body.username, .email = body.email, .pass_hash = password_hash, .family_id = family.?.id, .budget_id = budget.?.id, .created_at = now, .updated_at = now, .last_activity_at = now, .hide = 0 };
try db.insert(models.User, new_user);
const query = try models.createSelectOnFieldQuery(models.User, null, "pass_hash", "=");
user = try db.selectOne(models.User, allocator, query, .{ .pass_hash = password_hash });
if (user == null) {
handler.returnError("Unable to create new user", 500, res);
return;
}
// std.log.info("User created: {any}\nFamily created: {any}\n", .{ user.?, family.? });
const token = try auth.generateToken(user.?);
try handler.returnData(.{ .user = user, .token = token }, res);
}
pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void { pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb(); const db = handler.getDb();
@ -23,7 +223,10 @@ pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void {
const user = try db.selectOneById(models.User, allocator, id); const user = try db.selectOneById(models.User, allocator, id);
if (user == null) { if (user == null) {
handler.returnError("Error: User Not Found", 404, res); _ = auth.verifyRequest(req, res, id, user.family_id) catch {
return;
};
handler.returnError("Error: User Not Found of Forbidden", 403, res);
return; return;
} }
@ -31,7 +234,6 @@ pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void {
} }
const UserPostReq = struct { const UserPostReq = struct {
id: ?u32,
name: []const u8, name: []const u8,
family_id: u32, family_id: u32,
budget_id: u32, budget_id: u32,
@ -57,6 +259,10 @@ pub fn putUser(req: *httpz.Request, res: *httpz.Response) !void {
} }
var body = body_data.?; var body = body_data.?;
_ = auth.verifyRequest(req, res, body.id, body.family_id) catch {
return;
};
// Add User // Add User
const now = @intCast(u64, std.time.milliTimestamp()); const now = @intCast(u64, std.time.milliTimestamp());
// Update existing User // Update existing User
@ -76,74 +282,74 @@ pub fn putUser(req: *httpz.Request, res: *httpz.Response) !void {
// try res.json(user, .{}); // try res.json(user, .{});
} }
pub fn postUser(req: *httpz.Request, res: *httpz.Response) !void { // pub fn postUser(req: *httpz.Request, res: *httpz.Response) !void {
comptime { // comptime {
const putReqLen = @typeInfo(UserPostReq).Struct.fields.len; // const putReqLen = @typeInfo(UserPostReq).Struct.fields.len;
const userLen = @typeInfo(models.User).Struct.fields.len; // const userLen = @typeInfo(models.User).Struct.fields.len;
if (putReqLen != userLen) { // if (putReqLen != userLen) {
@compileError(std.fmt.comptimePrint("UserPutReq does not equal User model struct, fields inconsistent", .{})); // @compileError(std.fmt.comptimePrint("UserPutReq does not equal User model struct, fields inconsistent", .{}));
} // }
} // }
const db = handler.getDb(); // const db = handler.getDb();
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator(); // const allocator = gpa.allocator();
const body_data = req.json(UserPostReq) catch |err| { // const body_data = req.json(UserPostReq) catch |err| {
std.debug.print("Malformed body: {any}\n", .{err}); // std.debug.print("Malformed body: {any}\n", .{err});
handler.returnError("Bad request: Malformed Body", 400, res); // handler.returnError("Bad request: Malformed Body", 400, res);
return; // return;
}; // };
if (body_data == null) { // if (body_data == null) {
handler.returnError("Bad request: No Data", 400, res); // handler.returnError("Bad request: No Data", 400, res);
return; // return;
} // }
var body = body_data.?; // var body = body_data.?;
if (body.id != null) { // if (body.id != null) {
handler.returnError("Bad request: ID", 400, res); // handler.returnError("Bad request: ID", 400, res);
return; // return;
} // }
// Add User // // Add User
const now = @intCast(u64, std.time.milliTimestamp()); // const now = @intCast(u64, std.time.milliTimestamp());
// Create User // // Create User
body.created_at = now; // body.created_at = now;
body.last_activity_at = now; // body.last_activity_at = now;
body.updated_at = now; // body.updated_at = now;
try db.insert(models.User, utils.removeStructFields(body, &[_]u8{0})); // try db.insert(models.User, utils.removeStructFields(body, &[_]u8{0}));
// Get new User // // Get new User
const query = try models.createSelectOnFieldQuery(models.User, null, "created_at", "="); // const query = try models.createSelectOnFieldQuery(models.User, null, "created_at", "=");
const updated_user = try db.selectOne(models.User, allocator, query, .{ .created_at = body.created_at }); // const updated_user = try db.selectOne(models.User, allocator, query, .{ .created_at = body.created_at });
if (updated_user) |user| { // if (updated_user) |user| {
try handler.returnData(user, res); // try handler.returnData(user, res);
} else { // } else {
handler.returnError("Internal Server Error", 500, res); // handler.returnError("Internal Server Error", 500, res);
} // }
return; // return;
} // }
pub fn deleteUser(req: *httpz.Request, res: *httpz.Response) !void { // pub fn deleteUser(req: *httpz.Request, res: *httpz.Response) !void {
const db = handler.getDb(); // const db = handler.getDb();
const user_id = req.param("id"); // const user_id = req.param("id");
if (res.body) |_| { // if (res.body) |_| {
handler.returnError("Bad Request", 400, res); // handler.returnError("Bad Request", 400, res);
return; // return;
} // }
if (user_id) |id_str| { // if (user_id) |id_str| {
const id = std.fmt.parseInt(u32, id_str, 0) catch { // const id = std.fmt.parseInt(u32, id_str, 0) catch {
handler.returnError("Bad Request: Invalid Id", 400, res); // handler.returnError("Bad Request: Invalid Id", 400, res);
return; // return;
}; // };
db.deleteById(models.User, id) catch |err| { // db.deleteById(models.User, id) catch |err| {
std.debug.print("Error while deleting user: {}\n", .{err}); // std.debug.print("Error while deleting user: {}\n", .{err});
handler.returnError("Internal Server Error", 500, res); // handler.returnError("Internal Server Error", 500, res);
return; // return;
}; // };
} else { // } else {
handler.returnError("Bad Request: Missing ID", 400, res); // handler.returnError("Bad Request: Missing ID", 400, res);
} // }
return; // return;
} // }

View File

@ -1,11 +1,20 @@
const std = @import("std"); const std = @import("std");
const HASH_SEED: u64 = 6065983110;
const HASH_SALT: []const u8 = "ZnNLSRbY12DpPeMaPooKhOsxk7Qq325a2KF8EoIIeOaEz";
fn SpreadResult(comptime Base: type, comptime Additional: type) type { fn SpreadResult(comptime Base: type, comptime Additional: type) type {
comptime { comptime {
// const type_info = @typeInfo(Base); // const type_info = @typeInfo(Base);
// if (@Type(type_info) != std.builtin.Type.Struct) { // if (@Type(type_info) != std.builtin.Type.Struct) {
// @compileError("Cannot have anything but struct but got: " ++ @typeName(Base)); // @compileError("Cannot have anything but struct but got: " ++ @typeName(Base));
// } // }
if (@typeInfo(Base) != .Struct) {
@compileError("Provided non struct to struct concat: " ++ @typeName(Base));
}
if (@typeInfo(Additional) != .Struct) {
@compileError("Provided non struct to struct concat: " ++ @typeName(Additional));
}
// _ = std.fmt.comptimePrint("Passed in base: {} {}", .{ Base, type_info }); // _ = std.fmt.comptimePrint("Passed in base: {} {}", .{ Base, type_info });
} }
var fields = @typeInfo(Base).Struct.fields; var fields = @typeInfo(Base).Struct.fields;
@ -30,6 +39,11 @@ pub fn structConcatFields(
base: anytype, base: anytype,
additional: anytype, additional: anytype,
) SpreadResult(@TypeOf(base), @TypeOf(additional)) { ) SpreadResult(@TypeOf(base), @TypeOf(additional)) {
// comptime {
// if (@typeInfo(@TypeOf(base)) != .Struct or @typeInfo(@TypeOf(additional)) != .Struct) {
// @compileError("Provided non struct to struct concat");
// }
// }
const Base = @TypeOf(base); const Base = @TypeOf(base);
const Additional = @TypeOf(additional); const Additional = @TypeOf(additional);
var result: SpreadResult(Base, Additional) = undefined; var result: SpreadResult(Base, Additional) = undefined;
@ -95,12 +109,44 @@ pub fn removeStructFields(
return result; return result;
} }
pub fn generateRandomString(allocator: std.mem.Allocator) ![]const u8 {
const chars: []const u8 = "ABCDEFGHIJKJMNOPQRSTUVWXYZ1234567890";
var xoshiro = std.rand.DefaultPrng.init(@bitCast(u64, std.time.milliTimestamp()));
const rng = xoshiro.random();
var code: []u8 = try allocator.alloc(u8, 5);
for (0..code.len) |i| {
const char_index = rng.uintLessThan(u8, chars.len + 1) % chars.len;
code[i] = chars[char_index];
}
std.log.info("Generated family code: {s}", .{code});
return code;
}
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) !u32 {
const salted_password = try std.mem.concat(allocator, u8, &[_][]const u8{ password, HASH_SALT });
const password_hash = @truncate(u32, std.hash.Wyhash.hash(HASH_SEED, salted_password));
return password_hash;
}
test { test {
// const vote = .{ .id = 0, .createdAt = "DATE" }; // const vote = .{ .id = 0, .createdAt = "DATE" };
// const data = structConcatFields(vote, .{ .id2 = vote.id }); // const data = structConcatFields(vote, .{ .id2 = vote.id });
// std.log.err("\n{any}\n", .{data}); // std.log.err("\n{any}\n", .{data});
const user = .{ .id = 0, .createdAt = 2, .other = 3, .key = 4 }; // const user = .{ .id = 0, .createdAt = 2, .other = 3, .key = 4 };
const date = removeStructFields(user, &[_]u8{4}); // const date = removeStructFields(user, &[_]u8{4});
std.debug.print("\n{any}\n", .{date}); // std.debug.print("\n{any}\n", .{date});
var gpa = std.testing.allocator_instance;
// _ = gpa;
var allocator = gpa.allocator();
// _ = allocator;
// const code = try generateRandomString(allocator);
const hash = try hashPassword(allocator, "password");
std.debug.print("\nGot {}\n", .{hash});
} }

10
zerver.service Executable file
View File

@ -0,0 +1,10 @@
[Unit]
Description=Backend Zerver for RLuv app
After=network.target
[Service]
ExecStart=/home/mob/zerver/zig-out/bin/zerver --db_path /home/mob/zerver/data.db
Restart=always
[Install]
WantedBy=multi-user.target