Compare commits
10 Commits
81863ae912
...
2241b6f529
Author | SHA1 | Date | |
---|---|---|---|
2241b6f529 | |||
|
1f5698c3fd | ||
|
e1906dd9a8 | ||
7abe33ecaa | |||
|
6bba159e64 | ||
|
ef9a7d06e4 | ||
|
37250a15fb | ||
|
563b9023dc | ||
|
fb955625bc | ||
|
cdabcf61f3 |
17
data_backup.sh
Executable file
17
data_backup.sh
Executable 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
19
deploy.sh
Executable 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
1702
src/.deps/datetime.zig
Normal file
File diff suppressed because it is too large
Load Diff
677
src/.deps/timezones.zig
Normal file
677
src/.deps/timezones.zig
Normal 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);
|
||||
}
|
126
src/db/db.zig
126
src/db/db.zig
|
@ -11,9 +11,11 @@ pub const Db = struct {
|
|||
_mode: ?sqlite.Db.Mode,
|
||||
_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(.{
|
||||
.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 = .{
|
||||
.write = true,
|
||||
.create = true,
|
||||
|
@ -31,15 +33,51 @@ pub const Db = struct {
|
|||
self: *Db,
|
||||
comptime Type: type,
|
||||
allocator: Allocator,
|
||||
comptime whereClause: []const u8,
|
||||
comptime where_clause: []const u8,
|
||||
values: anytype,
|
||||
comptime limit: ?u32,
|
||||
comptime order_by_field: ?[]const u8,
|
||||
comptime order: ?[]const u8,
|
||||
comptime limit: ?bool,
|
||||
limit_val: ?u32,
|
||||
) !?[]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);
|
||||
|
||||
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ whereClause ++ ";";
|
||||
if (order_by_field == null and limit == null) {
|
||||
const query = "SELECT * FROM " ++ models.getTypeTableName(Type) ++ " " ++ where_clause ++ ";";
|
||||
var stmt = try self._sql_db.prepare(query);
|
||||
defer stmt.deinit();
|
||||
|
||||
|
@ -47,6 +85,34 @@ pub const Db = struct {
|
|||
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 ++ " 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();
|
||||
}
|
||||
|
@ -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 {
|
||||
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);
|
||||
// std.debug.print("{any}", .{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 {
|
||||
// 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| {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,31 @@
|
|||
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 {
|
||||
id: u32,
|
||||
|
@ -7,7 +33,7 @@ pub const Transaction = struct {
|
|||
type: []const u8,
|
||||
memo: ?[]const u8,
|
||||
budget_id: u32,
|
||||
added_by_user_id: u32,
|
||||
created_by_user_id: u32,
|
||||
budget_category_id: ?u32,
|
||||
date: u64,
|
||||
created_at: u64,
|
||||
|
@ -28,15 +54,20 @@ pub const BudgetCategory = struct {
|
|||
|
||||
pub const Budget = struct {
|
||||
id: u32,
|
||||
family_id: u32,
|
||||
name: []const u8,
|
||||
created_at: u64,
|
||||
updated_at: u64,
|
||||
hide: u8,
|
||||
expected_income: ?f64,
|
||||
};
|
||||
|
||||
pub const User = struct {
|
||||
id: u32,
|
||||
name: []const u8,
|
||||
username: ?[]const u8,
|
||||
email: ?[]const u8,
|
||||
pass_hash: u32,
|
||||
family_id: u32,
|
||||
budget_id: u32,
|
||||
created_at: u64,
|
||||
|
@ -47,13 +78,45 @@ pub const User = struct {
|
|||
|
||||
pub const Family = struct {
|
||||
id: u32,
|
||||
budget_id: u32,
|
||||
hide: u8,
|
||||
code: ?[]const u8,
|
||||
created_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
|
||||
pub inline fn createSelectOnIdQuery(comptime Type: type) []const u8 {
|
||||
|
@ -67,7 +130,11 @@ pub inline fn createSelectOnFieldQuery(
|
|||
comptime comparator: []const u8,
|
||||
) ![]const u8 {
|
||||
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;
|
||||
if (structField != null) {
|
||||
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 {
|
||||
return "DELETE from " ++ getTypeTableName(Type) ++ " WHERE id = ?;";
|
||||
}
|
||||
|
@ -89,14 +169,15 @@ pub inline fn createInsertQuery(comptime Type: type) []const u8 {
|
|||
var qs: []const u8 = "?";
|
||||
inline for (@typeInfo(Type).Struct.fields, 0..) |field, i| {
|
||||
// This is brittle, assumes 'id' struct field is first
|
||||
if (std.mem.eql(u8, field.name, "id")) {
|
||||
continue;
|
||||
}
|
||||
if (i > 1) {
|
||||
query = query ++ ", ";
|
||||
qs = qs ++ ", ?";
|
||||
}
|
||||
if (i != 0) {
|
||||
query = query ++ field.name;
|
||||
}
|
||||
}
|
||||
query = query ++ ") VALUES (" ++ qs ++ ");";
|
||||
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 {
|
||||
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 {
|
||||
comptime {
|
||||
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)) {
|
||||
.Int => "INTEGER NOT NULL",
|
||||
.Float => "REAL NOT NULL",
|
||||
|
@ -151,7 +254,14 @@ inline fn getSQLiteColumnMigrateText(comptime struct_field: std.builtin.Type.Str
|
|||
},
|
||||
.Array => "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;
|
||||
}
|
||||
|
@ -165,7 +275,14 @@ pub inline fn getTypeTableName(comptime Type: type) []const u8 {
|
|||
Budget => "budgets",
|
||||
BudgetCategory => "budget_categories",
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,55 @@ const ztime = @import(".deps/time.zig");
|
|||
const utils = @import("utils.zig");
|
||||
|
||||
const budget = @import("routes/budget.zig");
|
||||
const auth = @import("routes/auth.zig");
|
||||
const user = @import("routes/user.zig");
|
||||
const trans = @import("routes/transactions.zig");
|
||||
const dash = @import("routes/dashboard.zig");
|
||||
const note = @import("routes/shared_note.zig");
|
||||
|
||||
const Db = @import("db/db.zig").Db;
|
||||
|
||||
var db: Db = undefined;
|
||||
var db: ?Db = null;
|
||||
|
||||
pub fn getDb() *Db {
|
||||
return &db;
|
||||
return &db.?;
|
||||
}
|
||||
|
||||
pub fn startHttpServer() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
db = try Db.init(allocator, null);
|
||||
defer db.deinit();
|
||||
// db = try Db.init(allocator, null);
|
||||
// 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 });
|
||||
|
||||
|
@ -35,22 +66,30 @@ pub fn startHttpServer() !void {
|
|||
|
||||
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.delete("/user/:id", user.deleteUser);
|
||||
// router.delete("/user/:id", user.deleteUser);
|
||||
|
||||
router.get("/budget/:id", budget.getBudget);
|
||||
router.put("/budget", budget.putBudget);
|
||||
router.post("/budget", budget.postBudget);
|
||||
router.get("/shared_note/:limit", note.getSharedNotes);
|
||||
router.put("/shared_note", note.putSharedNote);
|
||||
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.put("/budget_category", budget.putBudgetCategory);
|
||||
router.delete("/budget_category", budget.deleteBudgetCategory);
|
||||
|
||||
router.get("/transactions/:budget_id", trans.getTransactions);
|
||||
router.post("/transactions", trans.postTransaction);
|
||||
router.put("/transactions", trans.putTransaction);
|
||||
router.post("/transaction", trans.postTransaction);
|
||||
router.put("/transaction", 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});
|
||||
// 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
|
||||
// must be valid beyond your handler. Use the res.arena if you need to allocate
|
||||
// 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`
|
||||
|
@ -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 {
|
||||
comptime {
|
||||
if (statusCode < 300 or statusCode > 500) {
|
||||
@compileError("Failed responses must have status codes between 300 and 500");
|
||||
if (statusCode > 500 or statusCode < 200) {
|
||||
@compileError("Failed responses must have status codes between 200 and 500");
|
||||
}
|
||||
}
|
||||
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});
|
||||
res.body = "{ \"success\": false";
|
||||
res.body = "{ \"success\": false }";
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -91,3 +134,23 @@ pub fn returnData(data: anytype, res: *httpz.Response) !void {
|
|||
res.status = 200;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,8 @@ const http = @import("./http_handler.zig");
|
|||
|
||||
pub fn main() !void {
|
||||
std.debug.print("\nStarting Server...\n", .{});
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
var db = try Db.init(allocator, null);
|
||||
defer db.deinit();
|
||||
// try db.wipeAndMigrateDb();
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const allocator = gpa.allocator();
|
||||
// _ = allocator;
|
||||
try http.startHttpServer();
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -3,122 +3,123 @@ 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 getBudget(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const db = handler.getDb();
|
||||
// pub fn getBudget(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
// const db = handler.getDb();
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
const id_str = req.param("id");
|
||||
if (id_str == null) {
|
||||
res.status = 400;
|
||||
res.body = "Bad Request: No Id";
|
||||
return;
|
||||
}
|
||||
const id = std.fmt.parseInt(u32, id_str.?, 0) catch {
|
||||
res.status = 401;
|
||||
res.body = "Bad Request: Bad Id";
|
||||
return;
|
||||
};
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const allocator = gpa.allocator();
|
||||
// const id_str = req.param("id");
|
||||
// if (id_str == null) {
|
||||
// res.status = 400;
|
||||
// res.body = "Bad Request: No Id";
|
||||
// return;
|
||||
// }
|
||||
// const id = std.fmt.parseInt(u32, id_str.?, 0) catch {
|
||||
// res.status = 401;
|
||||
// res.body = "Bad Request: Bad Id";
|
||||
// return;
|
||||
// };
|
||||
|
||||
const budget = try db.selectOneById(models.Budget, allocator, id);
|
||||
// const budget = try db.selectOneById(models.Budget, allocator, id);
|
||||
|
||||
if (budget == null) {
|
||||
res.status = 404;
|
||||
res.body = "Budget not found";
|
||||
return;
|
||||
}
|
||||
// if (budget == null) {
|
||||
// res.status = 404;
|
||||
// res.body = "Budget not found";
|
||||
// return;
|
||||
// }
|
||||
|
||||
try res.json(budget.?, .{});
|
||||
}
|
||||
// try res.json(budget.?, .{});
|
||||
// }
|
||||
|
||||
const BudgetPostReq = struct {
|
||||
id: ?u32,
|
||||
name: []const u8,
|
||||
created_at: ?u64,
|
||||
updated_at: ?u64,
|
||||
hide: u8,
|
||||
};
|
||||
// const BudgetPostReq = struct {
|
||||
// id: ?u32,
|
||||
// family_id: u32,
|
||||
// name: []const u8,
|
||||
// created_at: ?u64,
|
||||
// updated_at: ?u64,
|
||||
// hide: u8,
|
||||
// };
|
||||
|
||||
pub fn putBudget(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
var db = handler.getDb();
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
// pub fn putBudget(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.Budget) 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.?;
|
||||
// const body_data = req.json(models.Budget) 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.?;
|
||||
|
||||
// Add Budget
|
||||
const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// Update existing Budget
|
||||
body.updated_at = now;
|
||||
try db.updateById(models.Budget, body);
|
||||
// // Add Budget
|
||||
// const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// // Update existing Budget
|
||||
// body.updated_at = now;
|
||||
// try db.updateById(models.Budget, body);
|
||||
|
||||
const query = models.createSelectOnIdQuery(models.Transaction);
|
||||
const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .id = body.id });
|
||||
if (updated_budget) |budget| {
|
||||
try handler.returnData(budget, res);
|
||||
} else {
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// const query = models.createSelectOnIdQuery(models.Transaction);
|
||||
// const updated_budget = try db.selectOne(models.Budget, allocator, query, .{ .id = body.id });
|
||||
// if (updated_budget) |budget| {
|
||||
// try handler.returnData(budget, res);
|
||||
// } else {
|
||||
// handler.returnError("Internal Server Error", 500, res);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
pub fn postBudget(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
comptime {
|
||||
const putReqLen = @typeInfo(BudgetPostReq).Struct.fields.len;
|
||||
const budgetLen = @typeInfo(models.Budget).Struct.fields.len;
|
||||
if (putReqLen != budgetLen) {
|
||||
@compileError(std.fmt.comptimePrint("BudgetPutReq does not equal Budget model struct, fields inconsistent", .{}));
|
||||
}
|
||||
}
|
||||
// pub fn postBudget(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
// comptime {
|
||||
// const putReqLen = @typeInfo(BudgetPostReq).Struct.fields.len;
|
||||
// const budgetLen = @typeInfo(models.Budget).Struct.fields.len;
|
||||
// if (putReqLen != budgetLen) {
|
||||
// @compileError(std.fmt.comptimePrint("BudgetPostReq does not equal Budget model struct, fields inconsistent", .{}));
|
||||
// }
|
||||
// }
|
||||
|
||||
var db = handler.getDb();
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
// var db = handler.getDb();
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const allocator = gpa.allocator();
|
||||
|
||||
const body_data = req.json(BudgetPostReq) 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.?;
|
||||
// const body_data = req.json(BudgetPostReq) 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.?;
|
||||
|
||||
if (body.id != null) {
|
||||
handler.returnError("Bad Request: ID", 400, res);
|
||||
}
|
||||
// Add Budget
|
||||
const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// Create Budget
|
||||
body.created_at = now;
|
||||
body.updated_at = now;
|
||||
// if (body.id != null) {
|
||||
// handler.returnError("Bad Request: ID", 400, res);
|
||||
// }
|
||||
// // Add Budget
|
||||
// const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// // Create Budget
|
||||
// body.created_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
|
||||
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 });
|
||||
if (updated_budget) |budget| {
|
||||
try handler.returnData(budget, res);
|
||||
} else {
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// // Get Budget
|
||||
// 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 });
|
||||
// if (updated_budget) |budget| {
|
||||
// try handler.returnData(budget, res);
|
||||
// } else {
|
||||
// handler.returnError("Internal Server Error", 500, res);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
const BudgetCatPostReq = struct {
|
||||
id: ?u32,
|
||||
|
@ -160,6 +161,16 @@ pub fn postBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
handler.returnError("Bad request: ID", 400, res);
|
||||
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
|
||||
const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// Create Budget
|
||||
|
@ -170,15 +181,15 @@ pub fn postBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
|
||||
// Get Budget
|
||||
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 });
|
||||
if (updated_budget) |budget| {
|
||||
try handler.returnData(budget, res);
|
||||
} else {
|
||||
const updated_budget_cat = try db.selectOne(models.BudgetCategory, allocator, query, .{ .created_at = body.created_at });
|
||||
|
||||
if (updated_budget_cat == null) {
|
||||
std.debug.print("Could not find inserted budget", .{});
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try handler.returnData(updated_budget_cat.?, res);
|
||||
}
|
||||
|
||||
pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
var db = handler.getDb();
|
||||
|
@ -196,6 +207,15 @@ pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
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());
|
||||
|
||||
// Update existing Budget
|
||||
|
@ -203,12 +223,47 @@ pub fn putBudgetCategory(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
try db.updateById(models.BudgetCategory, budget_category);
|
||||
|
||||
const query = models.createSelectOnIdQuery(models.BudgetCategory);
|
||||
const updated_budget = try db.selectOne(models.BudgetCategory, allocator, query, .{ .id = budget_category.id });
|
||||
if (updated_budget) |budget| {
|
||||
try handler.returnData(budget, res);
|
||||
} else {
|
||||
const updated_budget_cat = try db.selectOne(models.BudgetCategory, allocator, query, .{ .id = budget_category.id });
|
||||
if (updated_budget_cat == null) {
|
||||
std.debug.print("Could not find inserted budget", .{});
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ const models = @import("../db/models.zig");
|
|||
const ztime = @import("../.deps/time.zig");
|
||||
const utils = @import("../utils.zig");
|
||||
const trans = @import("transactions.zig");
|
||||
const note = @import("shared_note.zig");
|
||||
const handler = @import("../http_handler.zig");
|
||||
const auth = @import("auth.zig");
|
||||
|
||||
pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const db = handler.getDb();
|
||||
|
@ -12,41 +14,62 @@ pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const family_id_str = req.param("family_id");
|
||||
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";
|
||||
const token = auth.verifyRequest(req, res, null, null) catch {
|
||||
return;
|
||||
};
|
||||
|
||||
const family = db.selectOneById(models.Family, allocator, family_id) catch |err| {
|
||||
if (err == error.SQLiteError) {
|
||||
res.status = 404;
|
||||
res.body = "Family Not Found";
|
||||
return;
|
||||
}
|
||||
std.debug.print("Error while getting family: {}\n", .{err});
|
||||
res.status = 500;
|
||||
res.body = "Internal Server Error";
|
||||
return;
|
||||
// const family_id = std.fmt.parseInt(u32, family_id_str.?, 0) catch {
|
||||
// res.status = 400;
|
||||
// res.body = "Bad Request: Bad FamilyId";
|
||||
// return;
|
||||
// };
|
||||
|
||||
const family = db.selectOneById(models.Family, allocator, token.family_id) catch |err| {
|
||||
std.log.err("Error getting family in dashboard: {any}", .{err});
|
||||
return handler.returnError("Unexpected Server Error", 500, res);
|
||||
};
|
||||
|
||||
if (family == null) {
|
||||
res.status = 404;
|
||||
res.body = "Family Not Found";
|
||||
return;
|
||||
std.log.err("Family not found, invalidating client token", .{});
|
||||
return handler.returnError("Family does not exist or forbidden", 403, res);
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
|
@ -54,9 +77,12 @@ pub fn getDashboard(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
const response_body = .{
|
||||
.family = family.?,
|
||||
.user = user.?,
|
||||
.budget = budget,
|
||||
.budget_categories = budget_categories,
|
||||
.transactions = transactions,
|
||||
.shared_notes = notes,
|
||||
.success = true,
|
||||
};
|
||||
try res.json(response_body, .{});
|
||||
}
|
||||
|
|
155
src/routes/shared_note.zig
Normal file
155
src/routes/shared_note.zig
Normal 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;
|
||||
}
|
|
@ -3,56 +3,84 @@ 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 time = @import("../.deps/datetime.zig");
|
||||
const tz = @import("../.deps/timezones.zig");
|
||||
|
||||
const auth = @import("auth.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();
|
||||
const now = ztime.DateTime.now();
|
||||
const beginningOfMonth = ztime.DateTime.init(now.years, now.months, 0, 0, 0, 0);
|
||||
const now = time.Datetime.now();
|
||||
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 {
|
||||
if (!std.mem.eql(u8, @typeInfo(models.Transaction).Struct.fields[7].name, "date")) {
|
||||
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,
|
||||
allocator,
|
||||
"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,
|
||||
) catch |err| {
|
||||
std.debug.print("Error while getting transactions query: {any}", .{err});
|
||||
return err;
|
||||
};
|
||||
return transactions;
|
||||
}
|
||||
|
||||
pub fn getTransactions(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
// pub fn getTransactions(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
// auth.verifyRequest();
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const allocator = gpa.allocator();
|
||||
|
||||
const budget_id_str = req.param("budget_id");
|
||||
if (budget_id_str == null) {
|
||||
handler.returnError("Bad request", 400, res);
|
||||
return;
|
||||
}
|
||||
const budget_id = std.fmt.parseInt(u32, budget_id_str.?, 0) catch {
|
||||
handler.returnError("Bad request", 400, res);
|
||||
return;
|
||||
};
|
||||
|
||||
const transactions = try fetchTransFromDb(allocator, budget_id);
|
||||
|
||||
if (transactions == null) {
|
||||
res.status = 200;
|
||||
res.body = "";
|
||||
return;
|
||||
}
|
||||
// std.debug.print("Transactions got:\n", .{});
|
||||
// for (transactions.?) |transaction| {
|
||||
// std.debug.print("\t{any}\n", .{transaction});
|
||||
// const budget_id_str = req.param("budget_id");
|
||||
// if (budget_id_str == null) {
|
||||
// handler.returnError("Bad request", 400, res);
|
||||
// return;
|
||||
// }
|
||||
// const budget_id = std.fmt.parseInt(u32, budget_id_str.?, 0) catch {
|
||||
// handler.returnError("Bad request", 400, res);
|
||||
// return;
|
||||
// };
|
||||
|
||||
// const transactions = try fetchTransFromDb(allocator, budget_id);
|
||||
|
||||
// if (transactions == null) {
|
||||
// res.status = 200;
|
||||
// res.body = "";
|
||||
// return;
|
||||
// }
|
||||
// // std.debug.print("Transactions got:\n", .{});
|
||||
// // for (transactions.?) |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 {
|
||||
var db = handler.getDb();
|
||||
|
@ -70,9 +98,16 @@ pub fn putTransaction(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
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());
|
||||
// Update existing Transaction
|
||||
transaction.updated_at = now;
|
||||
try db.updateById(models.Transaction, transaction);
|
||||
|
||||
|
@ -92,7 +127,7 @@ const TransPostReq = struct {
|
|||
type: []const u8,
|
||||
memo: ?[]const u8,
|
||||
budget_id: u32,
|
||||
added_by_user_id: u32,
|
||||
created_by_user_id: u32,
|
||||
budget_category_id: ?u32,
|
||||
date: u64,
|
||||
created_at: ?u64,
|
||||
|
@ -102,9 +137,9 @@ const TransPostReq = struct {
|
|||
|
||||
pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
comptime {
|
||||
const putReqLen = @typeInfo(TransPostReq).Struct.fields.len;
|
||||
const postReqLen = @typeInfo(TransPostReq).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", .{}));
|
||||
}
|
||||
}
|
||||
|
@ -112,26 +147,23 @@ pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const body_data = req.json(TransPostReq) catch |err| {
|
||||
std.debug.print("Malformed body: {any}\n", .{err});
|
||||
handler.returnError("Bad request: Malformed Body", 400, res);
|
||||
var body = handler.getReqJson(req, res, TransPostReq) catch {
|
||||
return;
|
||||
};
|
||||
if (body_data == null) {
|
||||
handler.returnError("Bad request: No Data", 400, res);
|
||||
return;
|
||||
}
|
||||
var body = body_data.?;
|
||||
|
||||
if (body.id != null) {
|
||||
handler.returnError("Bad request: ID", 400, res);
|
||||
return;
|
||||
const budget = try db.selectOneById(models.Budget, allocator, body.budget_id);
|
||||
if (budget == null) {
|
||||
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());
|
||||
body.created_at = now;
|
||||
body.updated_at = now;
|
||||
|
||||
// remove the null id field for insertion
|
||||
try db.insert(models.Transaction, utils.removeStructFields(body, &[_]u8{0}));
|
||||
|
||||
// Get new Transaction
|
||||
|
@ -144,3 +176,38 @@ pub fn postTransaction(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,208 @@ const std = @import("std");
|
|||
const httpz = @import("../.deps/http.zig/src/httpz.zig");
|
||||
const models = @import("../db/models.zig");
|
||||
const utils = @import("../utils.zig");
|
||||
|
||||
const auth = @import("auth.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 {
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -31,7 +234,6 @@ pub fn getUser(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
|
||||
const UserPostReq = struct {
|
||||
id: ?u32,
|
||||
name: []const u8,
|
||||
family_id: u32,
|
||||
budget_id: u32,
|
||||
|
@ -57,6 +259,10 @@ pub fn putUser(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
}
|
||||
var body = body_data.?;
|
||||
|
||||
_ = auth.verifyRequest(req, res, body.id, body.family_id) catch {
|
||||
return;
|
||||
};
|
||||
|
||||
// Add User
|
||||
const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// Update existing User
|
||||
|
@ -76,74 +282,74 @@ pub fn putUser(req: *httpz.Request, res: *httpz.Response) !void {
|
|||
// try res.json(user, .{});
|
||||
}
|
||||
|
||||
pub fn postUser(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
comptime {
|
||||
const putReqLen = @typeInfo(UserPostReq).Struct.fields.len;
|
||||
const userLen = @typeInfo(models.User).Struct.fields.len;
|
||||
if (putReqLen != userLen) {
|
||||
@compileError(std.fmt.comptimePrint("UserPutReq does not equal User model struct, fields inconsistent", .{}));
|
||||
}
|
||||
}
|
||||
// pub fn postUser(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
// comptime {
|
||||
// const putReqLen = @typeInfo(UserPostReq).Struct.fields.len;
|
||||
// const userLen = @typeInfo(models.User).Struct.fields.len;
|
||||
// if (putReqLen != userLen) {
|
||||
// @compileError(std.fmt.comptimePrint("UserPutReq does not equal User model struct, fields inconsistent", .{}));
|
||||
// }
|
||||
// }
|
||||
|
||||
const db = handler.getDb();
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
// const db = handler.getDb();
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
// const allocator = gpa.allocator();
|
||||
|
||||
const body_data = req.json(UserPostReq) 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.?;
|
||||
// const body_data = req.json(UserPostReq) 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.?;
|
||||
|
||||
if (body.id != null) {
|
||||
handler.returnError("Bad request: ID", 400, res);
|
||||
return;
|
||||
}
|
||||
// Add User
|
||||
const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// Create User
|
||||
body.created_at = now;
|
||||
body.last_activity_at = now;
|
||||
body.updated_at = now;
|
||||
// if (body.id != null) {
|
||||
// handler.returnError("Bad request: ID", 400, res);
|
||||
// return;
|
||||
// }
|
||||
// // Add User
|
||||
// const now = @intCast(u64, std.time.milliTimestamp());
|
||||
// // Create User
|
||||
// body.created_at = now;
|
||||
// body.last_activity_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
|
||||
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 });
|
||||
if (updated_user) |user| {
|
||||
try handler.returnData(user, res);
|
||||
} else {
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// // Get new User
|
||||
// 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 });
|
||||
// if (updated_user) |user| {
|
||||
// try handler.returnData(user, res);
|
||||
// } else {
|
||||
// handler.returnError("Internal Server Error", 500, res);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
pub fn deleteUser(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const db = handler.getDb();
|
||||
// pub fn deleteUser(req: *httpz.Request, res: *httpz.Response) !void {
|
||||
// const db = handler.getDb();
|
||||
|
||||
const user_id = req.param("id");
|
||||
if (res.body) |_| {
|
||||
handler.returnError("Bad Request", 400, res);
|
||||
return;
|
||||
}
|
||||
if (user_id) |id_str| {
|
||||
const id = std.fmt.parseInt(u32, id_str, 0) catch {
|
||||
handler.returnError("Bad Request: Invalid Id", 400, res);
|
||||
return;
|
||||
};
|
||||
db.deleteById(models.User, id) catch |err| {
|
||||
std.debug.print("Error while deleting user: {}\n", .{err});
|
||||
handler.returnError("Internal Server Error", 500, res);
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
handler.returnError("Bad Request: Missing ID", 400, res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// const user_id = req.param("id");
|
||||
// if (res.body) |_| {
|
||||
// handler.returnError("Bad Request", 400, res);
|
||||
// return;
|
||||
// }
|
||||
// if (user_id) |id_str| {
|
||||
// const id = std.fmt.parseInt(u32, id_str, 0) catch {
|
||||
// handler.returnError("Bad Request: Invalid Id", 400, res);
|
||||
// return;
|
||||
// };
|
||||
// db.deleteById(models.User, id) catch |err| {
|
||||
// std.debug.print("Error while deleting user: {}\n", .{err});
|
||||
// handler.returnError("Internal Server Error", 500, res);
|
||||
// return;
|
||||
// };
|
||||
// } else {
|
||||
// handler.returnError("Bad Request: Missing ID", 400, res);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
const std = @import("std");
|
||||
|
||||
const HASH_SEED: u64 = 6065983110;
|
||||
const HASH_SALT: []const u8 = "ZnNLSRbY12DpPeMaPooKhOsxk7Qq325a2KF8EoIIeOaEz";
|
||||
|
||||
fn SpreadResult(comptime Base: type, comptime Additional: type) type {
|
||||
comptime {
|
||||
// const type_info = @typeInfo(Base);
|
||||
// if (@Type(type_info) != std.builtin.Type.Struct) {
|
||||
// @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 });
|
||||
}
|
||||
var fields = @typeInfo(Base).Struct.fields;
|
||||
|
@ -30,6 +39,11 @@ pub fn structConcatFields(
|
|||
base: anytype,
|
||||
additional: anytype,
|
||||
) 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 Additional = @TypeOf(additional);
|
||||
var result: SpreadResult(Base, Additional) = undefined;
|
||||
|
@ -95,12 +109,44 @@ pub fn removeStructFields(
|
|||
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 {
|
||||
// const vote = .{ .id = 0, .createdAt = "DATE" };
|
||||
// const data = structConcatFields(vote, .{ .id2 = vote.id });
|
||||
// std.log.err("\n{any}\n", .{data});
|
||||
|
||||
const user = .{ .id = 0, .createdAt = 2, .other = 3, .key = 4 };
|
||||
const date = removeStructFields(user, &[_]u8{4});
|
||||
std.debug.print("\n{any}\n", .{date});
|
||||
// const user = .{ .id = 0, .createdAt = 2, .other = 3, .key = 4 };
|
||||
// const date = removeStructFields(user, &[_]u8{4});
|
||||
// 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
10
zerver.service
Executable 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
|
Loading…
Reference in New Issue
Block a user