added sqlite demo
This commit is contained in:
parent
9080196f6b
commit
cd5d269483
16
demo/sqlite/countries.sql
Normal file
16
demo/sqlite/countries.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.headers on
|
||||||
|
.mode table
|
||||||
|
|
||||||
|
WITH raw AS (
|
||||||
|
SELECT
|
||||||
|
value AS country
|
||||||
|
FROM json_each(readfile('j_countries.json'))
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
json_extract(country, '$.translations.deu.common') AS german_name,
|
||||||
|
json_extract(country, '$.population') AS population,
|
||||||
|
json_extract(country, '$.capital[0]') AS capital,
|
||||||
|
json_extract(country, '$.tld[0]') AS tld,
|
||||||
|
json_extract(country, '$.timezones') AS timezones
|
||||||
|
FROM raw
|
||||||
|
ORDER BY german_name;
|
||||||
19
demo/sqlite/export_csv_countries.sql
Normal file
19
demo/sqlite/export_csv_countries.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.mode csv
|
||||||
|
.headers on
|
||||||
|
.output result.csv
|
||||||
|
|
||||||
|
WITH raw AS (
|
||||||
|
SELECT
|
||||||
|
value AS country
|
||||||
|
FROM json_each(readfile('j_countries.json'))
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
json_extract(country, '$.translations.deu.common') AS german_name,
|
||||||
|
json_extract(country, '$.population') AS population,
|
||||||
|
json_extract(country, '$.capital[0]') AS capital,
|
||||||
|
json_extract(country, '$.timezones[0]') AS timezones
|
||||||
|
FROM raw
|
||||||
|
ORDER BY german_name;
|
||||||
|
|
||||||
|
.output stdout
|
||||||
1
demo/sqlite/j_countries.json
Normal file
1
demo/sqlite/j_countries.json
Normal file
File diff suppressed because one or more lines are too long
1
demo/sqlite/readfile.sql
Normal file
1
demo/sqlite/readfile.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT * FROM json_each(readfile('j_countries.json'));
|
||||||
196
demo/sqlite/result.csv
Normal file
196
demo/sqlite/result.csv
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
german_name,population,capital,timezones
|
||||||
|
Afghanistan,40218234,Kabul,UTC+04:30
|
||||||
|
Albanien,2837743,Tirana,UTC+01:00
|
||||||
|
Algerien,44700000,Algiers,UTC+01:00
|
||||||
|
Andorra,77265,"Andorra la Vella",UTC+01:00
|
||||||
|
Angola,32866268,Luanda,UTC+01:00
|
||||||
|
"Antigua und Barbuda",97928,"Saint John's",UTC-04:00
|
||||||
|
Argentinien,45376763,"Buenos Aires",UTC-03:00
|
||||||
|
Armenien,2963234,Yerevan,UTC+04:00
|
||||||
|
Aserbaidschan,10110116,Baku,UTC+04:00
|
||||||
|
Australien,25687041,Canberra,UTC+05:00
|
||||||
|
Bahamas,393248,Nassau,UTC-05:00
|
||||||
|
Bahrain,1701583,Manama,UTC+03:00
|
||||||
|
Bangladesch,164689383,Dhaka,UTC+06:00
|
||||||
|
Barbados,287371,Bridgetown,UTC-04:00
|
||||||
|
Belgien,11555997,Brussels,UTC+01:00
|
||||||
|
Belize,397621,Belmopan,UTC-06:00
|
||||||
|
Benin,12123198,Porto-Novo,UTC+01:00
|
||||||
|
Bhutan,771612,Thimphu,UTC+06:00
|
||||||
|
Bolivien,11673029,Sucre,UTC-04:00
|
||||||
|
"Bosnien und Herzegowina",3280815,Sarajevo,UTC+01:00
|
||||||
|
Botswana,2351625,Gaborone,UTC+02:00
|
||||||
|
Brasilien,212559409,"Brasília",UTC-05:00
|
||||||
|
Brunei,437483,"Bandar Seri Begawan",UTC+08:00
|
||||||
|
Bulgarien,6927288,Sofia,UTC+02:00
|
||||||
|
"Burkina Faso",20903278,Ouagadougou,UTC
|
||||||
|
Burundi,11890781,Gitega,UTC+02:00
|
||||||
|
Chile,19116209,Santiago,UTC-06:00
|
||||||
|
China,1402112000,Beijing,UTC+08:00
|
||||||
|
"Costa Rica",5094114,"San José",UTC-06:00
|
||||||
|
Deutschland,83240525,Berlin,UTC+01:00
|
||||||
|
Dominica,71991,Roseau,UTC-04:00
|
||||||
|
"Dominikanische Republik",10847904,"Santo Domingo",UTC-04:00
|
||||||
|
Dschibuti,988002,Djibouti,UTC+03:00
|
||||||
|
"Dänemark",5831404,Copenhagen,UTC-04:00
|
||||||
|
Ecuador,17643060,Quito,UTC-06:00
|
||||||
|
"El Salvador",6486201,"San Salvador",UTC-06:00
|
||||||
|
"Elfenbeinküste",26378275,Yamoussoukro,UTC
|
||||||
|
Eritrea,5352000,Asmara,UTC+03:00
|
||||||
|
Estland,1331057,Tallinn,UTC+02:00
|
||||||
|
Fidschi,896444,Suva,UTC+12:00
|
||||||
|
Finnland,5530719,Helsinki,UTC+02:00
|
||||||
|
Frankreich,67391582,Paris,UTC-10:00
|
||||||
|
Gabun,2225728,Libreville,UTC+01:00
|
||||||
|
Gambia,2416664,Banjul,UTC+00:00
|
||||||
|
Georgien,3714000,Tbilisi,UTC+04:00
|
||||||
|
Ghana,31072945,Accra,UTC
|
||||||
|
Grenada,112519,"St. George's",UTC-04:00
|
||||||
|
Griechenland,10715549,Athens,UTC+02:00
|
||||||
|
Guatemala,16858333,"Guatemala City",UTC-06:00
|
||||||
|
Guinea,13132792,Conakry,UTC
|
||||||
|
Guinea-Bissau,1967998,Bissau,UTC
|
||||||
|
Guyana,786559,Georgetown,UTC-04:00
|
||||||
|
Haiti,11402533,Port-au-Prince,UTC-05:00
|
||||||
|
Honduras,9904608,Tegucigalpa,UTC-06:00
|
||||||
|
Indien,1380004385,"New Delhi",UTC+05:30
|
||||||
|
Indonesien,273523621,Jakarta,UTC+07:00
|
||||||
|
Irak,40222503,Baghdad,UTC+03:00
|
||||||
|
Iran,83992953,Tehran,UTC+03:30
|
||||||
|
Irland,4994724,Dublin,UTC
|
||||||
|
Island,366425,Reykjavik,UTC
|
||||||
|
Israel,9216900,Jerusalem,UTC+02:00
|
||||||
|
Italien,59554023,Rome,UTC+01:00
|
||||||
|
Jamaika,2961161,Kingston,UTC-05:00
|
||||||
|
Japan,125836021,Tokyo,UTC+09:00
|
||||||
|
Jemen,29825968,"Sana'a",UTC+03:00
|
||||||
|
Jordanien,10203140,Amman,UTC+03:00
|
||||||
|
Kambodscha,16718971,"Phnom Penh",UTC+07:00
|
||||||
|
Kamerun,26545864,"Yaoundé",UTC+01:00
|
||||||
|
Kanada,38005238,Ottawa,UTC-08:00
|
||||||
|
"Kap Verde",555988,Praia,UTC-01:00
|
||||||
|
Kasachstan,18754440,Astana,UTC+05:00
|
||||||
|
Katar,2881060,Doha,UTC+03:00
|
||||||
|
Kenia,53771300,Nairobi,UTC+03:00
|
||||||
|
Kirgisistan,6591600,Bishkek,UTC+06:00
|
||||||
|
Kiribati,119446,"South Tarawa",UTC+12:00
|
||||||
|
Kolumbien,50882884,"Bogotá",UTC-05:00
|
||||||
|
Komoren,869595,Moroni,UTC+03:00
|
||||||
|
Kongo,5657000,Brazzaville,UTC+01:00
|
||||||
|
"Kongo (Dem. Rep.)",108407721,Kinshasa,UTC+01:00
|
||||||
|
Kosovo,1775378,Pristina,UTC+01:00
|
||||||
|
Kroatien,4047200,Zagreb,UTC+01:00
|
||||||
|
Kuba,11326616,Havana,UTC-05:00
|
||||||
|
Kuwait,4270563,"Kuwait City",UTC+03:00
|
||||||
|
Laos,7275556,Vientiane,UTC+07:00
|
||||||
|
Lesotho,2142252,Maseru,UTC+02:00
|
||||||
|
Lettland,1901548,Riga,UTC+02:00
|
||||||
|
Libanon,6825442,Beirut,UTC+02:00
|
||||||
|
Liberia,5057677,Monrovia,UTC
|
||||||
|
Libyen,6871287,Tripoli,UTC+01:00
|
||||||
|
Liechtenstein,38137,Vaduz,UTC+01:00
|
||||||
|
Litauen,2794700,Vilnius,UTC+02:00
|
||||||
|
Luxemburg,632275,Luxembourg,UTC+01:00
|
||||||
|
Madagaskar,27691019,Antananarivo,UTC+03:00
|
||||||
|
Malawi,19129955,Lilongwe,UTC+02:00
|
||||||
|
Malaysia,32365998,"Kuala Lumpur",UTC+08:00
|
||||||
|
Malediven,540542,"Malé",UTC+05:00
|
||||||
|
Mali,20250834,Bamako,UTC
|
||||||
|
Malta,525285,Valletta,UTC+01:00
|
||||||
|
Marokko,36910558,Rabat,UTC
|
||||||
|
Marshallinseln,59194,Majuro,UTC+12:00
|
||||||
|
Mauretanien,4649660,Nouakchott,UTC
|
||||||
|
Mauritius,1265740,"Port Louis",UTC+04:00
|
||||||
|
Mexiko,128932753,"Mexico City",UTC-08:00
|
||||||
|
Mikronesien,115021,Palikir,UTC+10:00
|
||||||
|
Moldawien,2617820,"Chișinău",UTC+02:00
|
||||||
|
Monaco,39244,Monaco,UTC+01:00
|
||||||
|
Mongolei,3278292,"Ulan Bator",UTC+07:00
|
||||||
|
Montenegro,621718,Podgorica,UTC+01:00
|
||||||
|
Mosambik,31255435,Maputo,UTC+02:00
|
||||||
|
Myanmar,54409794,Naypyidaw,UTC+06:30
|
||||||
|
Namibia,2540916,Windhoek,UTC+01:00
|
||||||
|
Nauru,10834,Yaren,UTC+12:00
|
||||||
|
Nepal,29136808,Kathmandu,UTC+05:45
|
||||||
|
Neuseeland,5084300,Wellington,UTC-11:00
|
||||||
|
Nicaragua,6624554,Managua,UTC-06:00
|
||||||
|
Niederlande,16655799,Amsterdam,UTC+01:00
|
||||||
|
Niger,24206636,Niamey,UTC+01:00
|
||||||
|
Nigeria,206139587,Abuja,UTC+01:00
|
||||||
|
Nordkorea,25778815,Pyongyang,UTC+09:00
|
||||||
|
Nordmazedonien,2077132,Skopje,UTC+01:00
|
||||||
|
Norwegen,5379475,Oslo,UTC+01:00
|
||||||
|
Oman,5106622,Muscat,UTC+04:00
|
||||||
|
Osttimor,1318442,Dili,UTC+09:00
|
||||||
|
Pakistan,220892331,Islamabad,UTC+05:00
|
||||||
|
Palau,18092,Ngerulmud,UTC+09:00
|
||||||
|
Panama,4314768,"Panama City",UTC-05:00
|
||||||
|
Papua-Neuguinea,8947027,"Port Moresby",UTC+10:00
|
||||||
|
Paraguay,7132530,"Asunción",UTC-04:00
|
||||||
|
Peru,32971846,Lima,UTC-05:00
|
||||||
|
Philippinen,109581085,Manila,UTC+08:00
|
||||||
|
Polen,37950802,Warsaw,UTC+01:00
|
||||||
|
Portugal,10305564,Lisbon,UTC-01:00
|
||||||
|
Ruanda,12952209,Kigali,UTC+02:00
|
||||||
|
"Rumänien",19286123,Bucharest,UTC+02:00
|
||||||
|
Russland,144104080,Moscow,UTC+03:00
|
||||||
|
Salomonen,686878,Honiara,UTC+11:00
|
||||||
|
Sambia,18383956,Lusaka,UTC+02:00
|
||||||
|
Samoa,198410,Apia,UTC+13:00
|
||||||
|
"San Marino",33938,"City of San Marino",UTC+01:00
|
||||||
|
Saudi-Arabien,34813867,Riyadh,UTC+03:00
|
||||||
|
Schweden,10353442,Stockholm,UTC+01:00
|
||||||
|
Schweiz,8654622,Bern,UTC+01:00
|
||||||
|
Senegal,16743930,Dakar,UTC
|
||||||
|
Serbien,6908224,Belgrade,UTC+01:00
|
||||||
|
Seychellen,98462,Victoria,UTC+04:00
|
||||||
|
"Sierra Leone",7976985,Freetown,UTC
|
||||||
|
Simbabwe,14862927,Harare,UTC+02:00
|
||||||
|
Singapur,5685807,Singapore,UTC+08:00
|
||||||
|
Slowakei,5458827,Bratislava,UTC+01:00
|
||||||
|
Slowenien,2100126,Ljubljana,UTC+01:00
|
||||||
|
Somalia,15893219,Mogadishu,UTC+03:00
|
||||||
|
Spanien,47351567,Madrid,UTC
|
||||||
|
"Sri Lanka",21919000,"Sri Jayawardenepura Kotte",UTC+05:30
|
||||||
|
"St. Kitts und Nevis",53192,Basseterre,UTC-04:00
|
||||||
|
"St. Lucia",183629,Castries,UTC-04:00
|
||||||
|
"St. Vincent und die Grenadinen",110947,Kingstown,UTC-04:00
|
||||||
|
Sudan,43849269,Khartoum,UTC+03:00
|
||||||
|
Suriname,586634,Paramaribo,UTC-03:00
|
||||||
|
Swasiland,1160164,Mbabane,UTC+02:00
|
||||||
|
Syrien,17500657,Damascus,UTC+02:00
|
||||||
|
"São Tomé und Príncipe",219161,"São Tomé",UTC
|
||||||
|
"Südafrika",59308690,Pretoria,UTC+02:00
|
||||||
|
"Südkorea",51780579,Seoul,UTC+09:00
|
||||||
|
"Südsudan",11193729,Juba,UTC+03:00
|
||||||
|
Tadschikistan,9537642,Dushanbe,UTC+05:00
|
||||||
|
Tansania,59734213,Dodoma,UTC+03:00
|
||||||
|
Thailand,69799978,Bangkok,UTC+07:00
|
||||||
|
Togo,8278737,"Lomé",UTC
|
||||||
|
Tonga,105697,"Nuku'alofa",UTC+13:00
|
||||||
|
"Trinidad und Tobago",1399491,"Port of Spain",UTC-04:00
|
||||||
|
Tschad,16425859,"N'Djamena",UTC+01:00
|
||||||
|
Tschechien,10698896,Prague,UTC+01:00
|
||||||
|
Tunesien,11818618,Tunis,UTC+01:00
|
||||||
|
Turkmenistan,6031187,Ashgabat,UTC+05:00
|
||||||
|
Tuvalu,11792,Funafuti,UTC+12:00
|
||||||
|
"Türkei",84339067,Ankara,UTC+03:00
|
||||||
|
Uganda,45741000,Kampala,UTC+03:00
|
||||||
|
Ukraine,44134693,Kyiv,UTC+02:00
|
||||||
|
Ungarn,9749763,Budapest,UTC+01:00
|
||||||
|
Uruguay,3473727,Montevideo,UTC-03:00
|
||||||
|
Usbekistan,34232050,Tashkent,UTC+05:00
|
||||||
|
Vanuatu,307150,"Port Vila",UTC+11:00
|
||||||
|
Vatikanstadt,451,"Vatican City",UTC+01:00
|
||||||
|
Venezuela,28435943,Caracas,UTC-04:00
|
||||||
|
"Vereinigte Arabische Emirate",9890400,"Abu Dhabi",UTC+04:00
|
||||||
|
"Vereinigte Staaten",329484123,"Washington, D.C.",UTC-12:00
|
||||||
|
"Vereinigtes Königreich",67215293,London,UTC-08:00
|
||||||
|
Vietnam,97338583,Hanoi,UTC+07:00
|
||||||
|
"Weißrussland",9398861,Minsk,UTC+03:00
|
||||||
|
"Zentralafrikanische Republik",4829764,Bangui,UTC+01:00
|
||||||
|
Zypern,1207361,Nicosia,UTC+02:00
|
||||||
|
"Ägypten",102334403,Cairo,UTC+02:00
|
||||||
|
"Äquatorialguinea",1402985,Malabo,UTC+01:00
|
||||||
|
"Äthiopien",114963583,"Addis Ababa",UTC+03:00
|
||||||
|
"Österreich",8917205,Vienna,UTC+01:00
|
||||||
|
70
demo/sqlite/sudoku.sql
Normal file
70
demo/sqlite/sudoku.sql
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
WITH RECURSIVE
|
||||||
|
input(sud) AS (
|
||||||
|
VALUES('53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79')
|
||||||
|
),
|
||||||
|
|
||||||
|
digits(z, lp) AS (
|
||||||
|
VALUES('1', 1)
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(lp+1 AS TEXT), lp+1 FROM digits WHERE lp<9
|
||||||
|
),
|
||||||
|
|
||||||
|
x(s, ind) AS (
|
||||||
|
SELECT sud, instr(sud, '.') FROM input
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
substr(s, 1, ind-1) || z || substr(s, ind+1),
|
||||||
|
instr(substr(s, 1, ind-1) || z || substr(s, ind+1), '.')
|
||||||
|
FROM x, digits AS z
|
||||||
|
WHERE ind>0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM digits AS lp
|
||||||
|
WHERE z.z = substr(s, ((ind-1)/9)*9 + lp, 1)
|
||||||
|
OR z.z = substr(s, ((ind-1)%9) + (lp-1)*9 + 1, 1)
|
||||||
|
OR z.z = substr(s, (((ind-1)/3) % 3)*3
|
||||||
|
+ ((ind-1)/27)*27
|
||||||
|
+ lp + ((lp-1)/3)*6, 1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
solved(s) AS (
|
||||||
|
SELECT s FROM x WHERE ind=0
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Build 9 pretty rows for both input and solved using digits.lp = 1..9
|
||||||
|
grid_rows(name, rn, line) AS (
|
||||||
|
SELECT 'input', d.lp,
|
||||||
|
substr(sud, (d.lp-1)*9+1, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(sud, (d.lp-1)*9+4, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(sud, (d.lp-1)*9+7, 3)
|
||||||
|
FROM input CROSS JOIN digits d
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT 'solved', d.lp,
|
||||||
|
substr(s, (d.lp-1)*9+1, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(s, (d.lp-1)*9+4, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(s, (d.lp-1)*9+7, 3)
|
||||||
|
FROM solved CROSS JOIN digits d
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Insert horizontal separators after rows 3 and 6 by computing an ordering key
|
||||||
|
grid_with_sep AS (
|
||||||
|
SELECT name, rn*2-1 AS ord, line FROM grid_rows
|
||||||
|
UNION ALL
|
||||||
|
SELECT name, rn*2 AS ord, '----+-----+----' FROM grid_rows WHERE rn IN (3,6)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Single final UNIONed selection, ordered by group + position
|
||||||
|
SELECT line FROM (
|
||||||
|
SELECT 1 AS grp, ord AS ord, line FROM grid_with_sep WHERE name='input'
|
||||||
|
UNION ALL
|
||||||
|
SELECT 2 AS grp, 0 AS ord, '' AS line
|
||||||
|
UNION ALL
|
||||||
|
SELECT 3 AS grp, ord AS ord, line FROM grid_with_sep WHERE name='solved'
|
||||||
|
) ORDER BY grp, ord;
|
||||||
14
lcars_v2/embed/create_state_db.sql
Normal file
14
lcars_v2/embed/create_state_db.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE ship_messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp TEXT NOT NULL,
|
||||||
|
subsystem TEXT NOT NULL,
|
||||||
|
severity TEXT CHECK(severity IN ('CRITICAL', 'ALERT', 'WARNING', 'NOTICE', 'INFO')) NOT NULL DEFAULT 'INFO',
|
||||||
|
color TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE crew_member (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
rank TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
95
lcars_v2/embed/crew.json
Normal file
95
lcars_v2/embed/crew.json
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
[
|
||||||
|
"Ensign Beckett Mariner",
|
||||||
|
"Ensign Brad Boimler",
|
||||||
|
"Captain Carol Freeman",
|
||||||
|
"Commander Jack Ransom",
|
||||||
|
"Ensign Barsa Orsino",
|
||||||
|
"Lieutenant Commander D'Vana Tendi",
|
||||||
|
"Lieutenant Commander Sam Rutherford",
|
||||||
|
"Lieutenant Commander Shaxs",
|
||||||
|
"Ensign Liora Vance",
|
||||||
|
"Ensign Rylan Sato",
|
||||||
|
"Lieutenant Jarek Torin",
|
||||||
|
"Lieutenant Kira Dallin",
|
||||||
|
"Lieutenant T'Lara Venn",
|
||||||
|
"Lieutenant Shonnie Velar",
|
||||||
|
"Lieutenant Commander Aric Thorne",
|
||||||
|
"Lieutenant Commander Selene Marvik",
|
||||||
|
"Lieutenant Commander Jovan Kreel",
|
||||||
|
"Lieutenant Orin Kallis",
|
||||||
|
"Ensign Mira Talon",
|
||||||
|
"Ensign Fynn Darvik",
|
||||||
|
"Lieutenant Commander Elara Voss",
|
||||||
|
"Lieutenant Zev Ralyn",
|
||||||
|
"Ensign Daxia Morn",
|
||||||
|
"Lieutenant Varek Solis",
|
||||||
|
"Ensign Tylen Kael",
|
||||||
|
"Lieutenant Commander Nira Falco",
|
||||||
|
"Lieutenant Kael Dorran",
|
||||||
|
"Ensign Saren Vale",
|
||||||
|
"Ensign Tova Lin",
|
||||||
|
"Lieutenant Commander Ryn Talor",
|
||||||
|
"Lieutenant Draven Korr",
|
||||||
|
"Ensign Lyra Kenning",
|
||||||
|
"Ensign Joren Pax",
|
||||||
|
"Lieutenant Commander Calix Arden",
|
||||||
|
"Lieutenant Selan Vey",
|
||||||
|
"Ensign Aricel Taren",
|
||||||
|
"Ensign Velin Daro",
|
||||||
|
"Lieutenant Caris Vennor",
|
||||||
|
"Lieutenant Kellen Dray",
|
||||||
|
"Lieutenant Risa Talven",
|
||||||
|
"Lieutenant Commander Thalen Voss",
|
||||||
|
"Lieutenant Commander Sariah Quell",
|
||||||
|
"Ensign Orin Talvik",
|
||||||
|
"Ensign Lyric Selden",
|
||||||
|
"Lieutenant Commander Varen Korr",
|
||||||
|
"Lieutenant Elara Vynn",
|
||||||
|
"Ensign Jax Talmar",
|
||||||
|
"Lieutenant Commander Neris Vay",
|
||||||
|
"Lieutenant Draven Solis",
|
||||||
|
"Ensign Tavia Korlen",
|
||||||
|
"Ensign Ryn Paxil",
|
||||||
|
"Lieutenant Commander Kira Dalen",
|
||||||
|
"Lieutenant Zev Ardin",
|
||||||
|
"Ensign Lyra Taven",
|
||||||
|
"Ensign Fynn Velar",
|
||||||
|
"Lieutenant Commander Calen Rhos",
|
||||||
|
"Lieutenant Selan Vaylen",
|
||||||
|
"Ensign Aricel Dorran",
|
||||||
|
"Ensign Tylen Korr",
|
||||||
|
"Lieutenant Commander Nira Talos",
|
||||||
|
"Lieutenant Kael Venn",
|
||||||
|
"Ensign Saren Daro",
|
||||||
|
"Ensign Tova Vennor",
|
||||||
|
"Lieutenant Commander Ryn Arden",
|
||||||
|
"Lieutenant Draven Voss",
|
||||||
|
"Ensign Lyra Talin",
|
||||||
|
"Ensign Joren Vay",
|
||||||
|
"Lieutenant Commander Calix Talven",
|
||||||
|
"Lieutenant Selan Korr",
|
||||||
|
"Ensign Aricel Vynn",
|
||||||
|
"Ensign Velin Talor",
|
||||||
|
"Lieutenant Caris Vaylen",
|
||||||
|
"Lieutenant Kellen Rhos",
|
||||||
|
"Lieutenant Risa Vennor",
|
||||||
|
"Lieutenant Commander Thalen Daro",
|
||||||
|
"Lieutenant Commander Sariah Voss",
|
||||||
|
"Ensign Orin Vaylen",
|
||||||
|
"Ensign Lyric Talven",
|
||||||
|
"Lieutenant Commander Varen Talos",
|
||||||
|
"Lieutenant Elara Solis",
|
||||||
|
"Ensign Jax Vennor",
|
||||||
|
"Lieutenant Commander Neris Talor",
|
||||||
|
"Lieutenant Draven Vaylen",
|
||||||
|
"Ensign Tavia Dorran",
|
||||||
|
"Ensign Ryn Voss",
|
||||||
|
"Lieutenant Commander Kira Arden",
|
||||||
|
"Lieutenant Zev Vay",
|
||||||
|
"Ensign Lyra Daro",
|
||||||
|
"Ensign Fynn Talor",
|
||||||
|
"Lieutenant Commander Calen Venn",
|
||||||
|
"Lieutenant Selan Talvik",
|
||||||
|
"Ensign Aricel Rhos"
|
||||||
|
]
|
||||||
|
|
||||||
1066
lcars_v2/embed/messages.json
Normal file
1066
lcars_v2/embed/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
312
lcars_v2/eventbus/eventbus.go
Normal file
312
lcars_v2/eventbus/eventbus.go
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
// modified version, see original:
|
||||||
|
// https://github.com/dtomasi/go-event-bus/blob/main/event_bus.go
|
||||||
|
|
||||||
|
|
||||||
|
package eventbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Rec map[string]interface{}
|
||||||
|
|
||||||
|
|
||||||
|
// Event holds topic name and data.
|
||||||
|
type Event struct {
|
||||||
|
Data Rec
|
||||||
|
Topic string
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done calls Done on sync.WaitGroup if set.
|
||||||
|
func (e *Event) Done() {
|
||||||
|
if e.wg != nil {
|
||||||
|
e.wg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallbackFunc Defines a CallbackFunc.
|
||||||
|
type CallbackFunc func(topic string, data Rec)
|
||||||
|
|
||||||
|
// EventChannel is a channel which can accept an Event.
|
||||||
|
type EventChannel chan Event
|
||||||
|
|
||||||
|
// NewEventChannel Creates a new EventChannel.
|
||||||
|
func NewEventChannel() EventChannel {
|
||||||
|
return make(EventChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataChannelSlice is a slice of DataChannels.
|
||||||
|
type eventChannelSlice []EventChannel
|
||||||
|
|
||||||
|
// EventBus stores the information about subscribers interested for a particular topic.
|
||||||
|
type EventBus struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
subscribers map[string]eventChannelSlice
|
||||||
|
stats *Stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventBus returns a new EventBus instance.
|
||||||
|
func NewEventBus() *EventBus {
|
||||||
|
return &EventBus{ //nolint:exhaustivestruct
|
||||||
|
subscribers: map[string]eventChannelSlice{},
|
||||||
|
stats: newStats(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubscribingChannels returns all subscribing channels including wildcard matches.
|
||||||
|
func (eb *EventBus) getSubscribingChannels(topic string) eventChannelSlice {
|
||||||
|
subChannels := eventChannelSlice{}
|
||||||
|
|
||||||
|
for topicName := range eb.subscribers {
|
||||||
|
if topicName == topic || matchWildcard(topicName, topic) {
|
||||||
|
subChannels = append(subChannels, eb.subscribers[topicName]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subChannels
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPublish is publishing events to channels internally.
|
||||||
|
func (eb *EventBus) doPublish(channels eventChannelSlice, evt Event) {
|
||||||
|
eb.mu.RLock()
|
||||||
|
defer eb.mu.RUnlock()
|
||||||
|
|
||||||
|
go func(channels eventChannelSlice, evt Event) {
|
||||||
|
for _, ch := range channels {
|
||||||
|
ch <- evt
|
||||||
|
}
|
||||||
|
}(channels, evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code from https://github.com/minio/minio/blob/master/pkg/wildcard/match.go
|
||||||
|
func matchWildcard(pattern, name string) bool {
|
||||||
|
if pattern == "" {
|
||||||
|
return name == pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
if pattern == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Does only wildcard '*' match.
|
||||||
|
return deepMatchRune([]rune(name), []rune(pattern), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code from https://github.com/minio/minio/blob/master/pkg/wildcard/match.go
|
||||||
|
func deepMatchRune(str, pattern []rune, simple bool) bool { //nolint:unparam
|
||||||
|
for len(pattern) > 0 {
|
||||||
|
switch pattern[0] {
|
||||||
|
default:
|
||||||
|
if len(str) == 0 || str[0] != pattern[0] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case '*':
|
||||||
|
return deepMatchRune(str, pattern[1:], simple) ||
|
||||||
|
(len(str) > 0 && deepMatchRune(str[1:], pattern, simple))
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str[1:]
|
||||||
|
|
||||||
|
pattern = pattern[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(str) == 0 && len(pattern) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishAsync data to a topic asynchronously
|
||||||
|
// This function returns a bool channel which indicates that all subscribers where called.
|
||||||
|
func (eb *EventBus) PublishAsync(topic string, data Rec) {
|
||||||
|
eb.doPublish(
|
||||||
|
eb.getSubscribingChannels(topic),
|
||||||
|
Event{
|
||||||
|
Data: data,
|
||||||
|
Topic: topic,
|
||||||
|
wg: nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
eb.stats.incPublishedCountByTopic(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishAsyncOnce same as PublishAsync but makes sure that topic is only published once.
|
||||||
|
func (eb *EventBus) PublishAsyncOnce(topic string, data Rec) {
|
||||||
|
if eb.stats.GetPublishedCountByTopic(topic) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eb.PublishAsync(topic, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish data to a topic and wait for all subscribers to finish
|
||||||
|
// This function creates a waitGroup internally. All subscribers must call Done() function on Event.
|
||||||
|
func (eb *EventBus) Publish(topic string, data Rec) Rec {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
channels := eb.getSubscribingChannels(topic)
|
||||||
|
wg.Add(len(channels))
|
||||||
|
eb.doPublish(
|
||||||
|
channels,
|
||||||
|
Event{
|
||||||
|
Data: data,
|
||||||
|
Topic: topic,
|
||||||
|
wg: &wg,
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
eb.stats.incPublishedCountByTopic(topic)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublishOnce same as Publish but makes sure only published once on topic.
|
||||||
|
func (eb *EventBus) PublishOnce(topic string, data Rec) Rec {
|
||||||
|
if eb.stats.GetPublishedCountByTopic(topic) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb.Publish(topic, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to a topic passing a EventChannel.
|
||||||
|
func (eb *EventBus) Subscribe(topic string) EventChannel {
|
||||||
|
ch := make(EventChannel)
|
||||||
|
eb.SubscribeChannel(topic, ch)
|
||||||
|
|
||||||
|
eb.stats.incSubscriberCountByTopic(topic)
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeChannel subscribes to a given Channel.
|
||||||
|
func (eb *EventBus) SubscribeChannel(topic string, ch EventChannel) {
|
||||||
|
eb.mu.Lock()
|
||||||
|
defer eb.mu.Unlock()
|
||||||
|
|
||||||
|
if prev, found := eb.subscribers[topic]; found {
|
||||||
|
eb.subscribers[topic] = append(prev, ch)
|
||||||
|
} else {
|
||||||
|
eb.subscribers[topic] = append([]EventChannel{}, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
eb.stats.incSubscriberCountByTopic(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeCallback provides a simple wrapper that allows to directly register CallbackFunc instead of channels.
|
||||||
|
func (eb *EventBus) SubscribeCallback(topic string, callable CallbackFunc) {
|
||||||
|
ch := NewEventChannel()
|
||||||
|
eb.SubscribeChannel(topic, ch)
|
||||||
|
|
||||||
|
go func(callable CallbackFunc) {
|
||||||
|
evt := <-ch
|
||||||
|
callable(evt.Topic, evt.Data)
|
||||||
|
evt.Done()
|
||||||
|
}(callable)
|
||||||
|
|
||||||
|
eb.stats.incSubscriberCountByTopic(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSubscribers Check if a topic has subscribers.
|
||||||
|
func (eb *EventBus) HasSubscribers(topic string) bool {
|
||||||
|
return len(eb.getSubscribingChannels(topic)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns the stats map.
|
||||||
|
func (eb *EventBus) Stats() *Stats {
|
||||||
|
return eb.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopicStats struct {
|
||||||
|
Name string
|
||||||
|
PublishedCount *SafeCounter
|
||||||
|
SubscriberCount *SafeCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
type topicStatsMap map[string]*TopicStats
|
||||||
|
|
||||||
|
type Stats struct {
|
||||||
|
data topicStatsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStats() *Stats {
|
||||||
|
return &Stats{
|
||||||
|
data: map[string]*TopicStats{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) getOrCreateTopicStats(topicName string) *TopicStats {
|
||||||
|
_, ok := s.data[topicName]
|
||||||
|
if !ok {
|
||||||
|
s.data[topicName] = &TopicStats{
|
||||||
|
Name: topicName,
|
||||||
|
PublishedCount: NewSafeCounter(),
|
||||||
|
SubscriberCount: NewSafeCounter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.data[topicName]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) incSubscriberCountByTopic(topicName string) {
|
||||||
|
s.getOrCreateTopicStats(topicName).SubscriberCount.Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) GetSubscriberCountByTopic(topicName string) int {
|
||||||
|
return s.getOrCreateTopicStats(topicName).SubscriberCount.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) incPublishedCountByTopic(topicName string) {
|
||||||
|
s.getOrCreateTopicStats(topicName).PublishedCount.Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) GetPublishedCountByTopic(topicName string) int {
|
||||||
|
return s.getOrCreateTopicStats(topicName).PublishedCount.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) GetTopicStats() []*TopicStats {
|
||||||
|
var tStatsSlice []*TopicStats
|
||||||
|
for _, tStats := range s.data {
|
||||||
|
tStatsSlice = append(tStatsSlice, tStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tStatsSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) GetTopicStatsByName(topicName string) *TopicStats {
|
||||||
|
return s.getOrCreateTopicStats(topicName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeCounter is a concurrency safe counter.
|
||||||
|
type SafeCounter struct {
|
||||||
|
v *uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSafeCounter creates a new counter.
|
||||||
|
func NewSafeCounter() *SafeCounter {
|
||||||
|
return &SafeCounter{
|
||||||
|
v: new(uint64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the current value.
|
||||||
|
func (c *SafeCounter) Value() int {
|
||||||
|
return int(atomic.LoadUint64(c.v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncBy increments the counter by given delta.
|
||||||
|
func (c *SafeCounter) IncBy(add uint) {
|
||||||
|
atomic.AddUint64(c.v, uint64(add))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc increments the counter by 1.
|
||||||
|
func (c *SafeCounter) Inc() {
|
||||||
|
c.IncBy(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecBy decrements the counter by given delta.
|
||||||
|
func (c *SafeCounter) DecBy(dec uint) {
|
||||||
|
atomic.AddUint64(c.v, ^uint64(dec-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec decrements the counter by 1.
|
||||||
|
func (c *SafeCounter) Dec() {
|
||||||
|
c.DecBy(1)
|
||||||
|
}
|
||||||
102
lcars_v2/experiments.go
Normal file
102
lcars_v2/experiments.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"ld/server"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readCrew(server *server.Server) (string, error) {
|
||||||
|
|
||||||
|
content, err := embedded.ReadFile("embed/crew.json")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a slice to hold the parsed names
|
||||||
|
var crewNames []string
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
err = json.Unmarshal(content, &crewNames)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error parsing JSON:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertStmt = "INSERT INTO crew_member ( rank, name) VALUES (?, ?) ;"
|
||||||
|
insstmt, err := server.StateDB.DB().Prepare(insertStmt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer insstmt.Close()
|
||||||
|
|
||||||
|
// Print the results
|
||||||
|
for _, text := range crewNames {
|
||||||
|
rank, name := splitRank(text)
|
||||||
|
if rank == "" {
|
||||||
|
rank = "ERROR"
|
||||||
|
}
|
||||||
|
// fmt.Printf("%d: rank: %s name: %s\n", i+1, rank, name)
|
||||||
|
_, err = insstmt.Exec(rank, name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fmt.Println(string(content))
|
||||||
|
return "", nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitRank separates the rank (all tokens except the last two) from the crewman's name (last two tokens)
|
||||||
|
func splitRank(fullName string) (rank, name string) {
|
||||||
|
tokens := strings.Fields(fullName)
|
||||||
|
if len(tokens) < 2 {
|
||||||
|
return fullName, "" // fallback if malformed
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTokens := tokens[len(tokens)-2:] // last 2 tokens as name
|
||||||
|
rankTokens := tokens[:len(tokens)-2] // everything else as rank
|
||||||
|
name = strings.Join(nameTokens, " ")
|
||||||
|
rank = strings.Join(rankTokens, " ")
|
||||||
|
|
||||||
|
return rank, name
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Subsystem string `json:"subsystem"`
|
||||||
|
Severity string `json:"severity"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMessages(server *server.Server) error {
|
||||||
|
content, err := embedded.ReadFile("embed/messages.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var messages []Message
|
||||||
|
if err := json.Unmarshal(content, &messages); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertStmt = "INSERT INTO ship_messages ( timestamp, subsystem, severity, color, message) VALUES (?,?,?,?,?) ;"
|
||||||
|
insstmt, err := server.StateDB.DB().Prepare(insertStmt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer insstmt.Close()
|
||||||
|
// For demonstration, print the parsed messages
|
||||||
|
for _, m := range messages {
|
||||||
|
// fmt.Printf("[%s] %s (%s) - %s\n", m.Timestamp, m.Subsystem, m.Severity, m.Message)
|
||||||
|
_, err = insstmt.Exec(m.Timestamp, m.Subsystem, m.Severity, m.Color, m.Message)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
BIN
lcars_v2/frontend/assets/Antonio-Bold.woff
Normal file
BIN
lcars_v2/frontend/assets/Antonio-Bold.woff
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/Antonio-Bold.woff2
Normal file
BIN
lcars_v2/frontend/assets/Antonio-Bold.woff2
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/Antonio-Regular.woff
Normal file
BIN
lcars_v2/frontend/assets/Antonio-Regular.woff
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/Antonio-Regular.woff2
Normal file
BIN
lcars_v2/frontend/assets/Antonio-Regular.woff2
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/alert.png
Normal file
BIN
lcars_v2/frontend/assets/alert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
lcars_v2/frontend/assets/beep1.mp3
Normal file
BIN
lcars_v2/frontend/assets/beep1.mp3
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/beep2.mp3
Normal file
BIN
lcars_v2/frontend/assets/beep2.mp3
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/beep3.mp3
Normal file
BIN
lcars_v2/frontend/assets/beep3.mp3
Normal file
Binary file not shown.
BIN
lcars_v2/frontend/assets/beep4.mp3
Normal file
BIN
lcars_v2/frontend/assets/beep4.mp3
Normal file
Binary file not shown.
3011
lcars_v2/frontend/assets/classic.css
Normal file
3011
lcars_v2/frontend/assets/classic.css
Normal file
File diff suppressed because it is too large
Load Diff
51
lcars_v2/frontend/assets/lcars.js
Normal file
51
lcars_v2/frontend/assets/lcars.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
document.addEventListener("touchstart", function() {},false);
|
||||||
|
let mybutton = document.getElementById("topBtn");
|
||||||
|
window.onscroll = function() {scrollFunction()};
|
||||||
|
function scrollFunction() {
|
||||||
|
if (document.body.scrollTop > 200 || document.documentElement.scrollTop > 200) {
|
||||||
|
mybutton.style.display = "block";
|
||||||
|
} else {
|
||||||
|
mybutton.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function topFunction() {
|
||||||
|
document.body.scrollTop = 0;
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
}
|
||||||
|
function playSoundAndRedirect(audioId, url) {
|
||||||
|
// var audio = document.getElementById(audioId);
|
||||||
|
// audio.play();
|
||||||
|
|
||||||
|
// audio.onended = function() {
|
||||||
|
// console.log("Audio has ended", url);
|
||||||
|
window.location.href = url;
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
function goToAnchor(anchorId) {
|
||||||
|
window.location.hash = anchorId;
|
||||||
|
}
|
||||||
|
// Accordion drop-down
|
||||||
|
var acc = document.getElementsByClassName("accordion");
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 0; i < acc.length; i++) {
|
||||||
|
acc[i].addEventListener("click", function() {
|
||||||
|
this.classList.toggle("active");
|
||||||
|
var accordionContent = this.nextElementSibling;
|
||||||
|
if (accordionContent.style.maxHeight){
|
||||||
|
accordionContent.style.maxHeight = null;
|
||||||
|
} else {
|
||||||
|
accordionContent.style.maxHeight = accordionContent.scrollHeight + "px";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// LCARS keystroke sound (not to be used with hyperlinks)
|
||||||
|
const LCARSkeystroke = document.getElementById('LCARSkeystroke');
|
||||||
|
const allPlaySoundButtons = document.querySelectorAll('.playSoundButton');
|
||||||
|
allPlaySoundButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
LCARSkeystroke.pause();
|
||||||
|
LCARSkeystroke.currentTime = 0; // Reset to the beginning of the sound
|
||||||
|
LCARSkeystroke.play();
|
||||||
|
});
|
||||||
|
});
|
||||||
1885
lcars_v2/frontend/assets/lower-decks-padd.css
Normal file
1885
lcars_v2/frontend/assets/lower-decks-padd.css
Normal file
File diff suppressed because it is too large
Load Diff
1856
lcars_v2/frontend/assets/lower-decks.css
Normal file
1856
lcars_v2/frontend/assets/lower-decks.css
Normal file
File diff suppressed because it is too large
Load Diff
2830
lcars_v2/frontend/assets/nemesis-blue.css
Normal file
2830
lcars_v2/frontend/assets/nemesis-blue.css
Normal file
File diff suppressed because it is too large
Load Diff
231
lcars_v2/frontend/crew/index.html
Normal file
231
lcars_v2/frontend/crew/index.html
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Lower Decks PADD</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="format-detection" content="date=no">
|
||||||
|
<link rel="stylesheet" type="text/css" href="../assets/lower-decks-padd.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- <audio id="audio1" src="assets/beep1.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio2" src="assets/beep2.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio3" src="assets/beep3.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio4" src="assets/beep4.mp3" preload="auto"></audio> -->
|
||||||
|
<div class="wrap-all">
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="left-frame-top">
|
||||||
|
<!--
|
||||||
|
*** LCARS PANEL BUTTON ***
|
||||||
|
Replace the hashtag '#' with a real URL (or not) in the following <button> tag. If you do not want a sound effect for this link, replace the <button> element with the following <div> + <a> elements:
|
||||||
|
|
||||||
|
<div class="panel-1">
|
||||||
|
<a href="#">LCARS</a>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<a href="/" class="panel-1-button">LCARS</a>
|
||||||
|
<!-- <button onclick="playSoundAndRedirect('audio2', '/')" class="panel-1-button">LCARS</button> -->
|
||||||
|
<div class="panel-2">02<span class="hop">-262000</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="right-frame-top">
|
||||||
|
<div class="banner">LCARS 57436.2</div>
|
||||||
|
<div class="data-cascade-button-group">
|
||||||
|
<div class="data-wrapper">
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">47</div>
|
||||||
|
<div class="dc-row-2">31</div>
|
||||||
|
<div class="dc-row-3">28</div>
|
||||||
|
<div class="dc-row-4">94</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">329</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">128</div>
|
||||||
|
<div class="dc-row-3">605</div>
|
||||||
|
<div class="dc-row-4">704</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-night-rain">39725514862</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">51320259663</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">21857221984</div>
|
||||||
|
<div class="dc-row-4">40372566301</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">56</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">04</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">40</div>
|
||||||
|
<div class="dc-row-4 font-night-rain">35</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">614</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">883</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">109</div>
|
||||||
|
<div class="dc-row-4">297</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 darkspace darkfont">000</div>
|
||||||
|
<div class="dc-row-2 darkspace font-alpha-blue">13</div>
|
||||||
|
<div class="dc-row-3 darkspace font-arctic-ice">05</div>
|
||||||
|
<div class="dc-row-4 darkspace font-night-rain">25</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">48</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">07</div>
|
||||||
|
<div class="dc-row-3">38</div>
|
||||||
|
<div class="dc-row-4">62</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">416</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">001</div>
|
||||||
|
<div class="dc-row-3">888</div>
|
||||||
|
<div class="dc-row-4">442</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-night-rain">86225514862</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">31042009183</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">74882306985</div>
|
||||||
|
<div class="dc-row-4">54048523421</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-alpha-blue">10</div>
|
||||||
|
<div class="dc-row-2">80</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">31</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">85</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-alpha-blue">87</div>
|
||||||
|
<div class="dc-row-2">71</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">40</div>
|
||||||
|
<div class="dc-row-4 font-night-rain">26</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">98</div>
|
||||||
|
<div class="dc-row-2">63</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">52</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">71</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">118</div>
|
||||||
|
<div class="dc-row-2">270</div>
|
||||||
|
<div class="dc-row-3">395</div>
|
||||||
|
<div class="dc-row-4">260</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">8675309</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">7952705</div>
|
||||||
|
<div class="dc-row-3">9282721</div>
|
||||||
|
<div class="dc-row-4">4981518</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 darkspace darkfont">000</div>
|
||||||
|
<div class="dc-row-2 darkspace font-alpha-blue">99</div>
|
||||||
|
<div class="dc-row-3 darkspace font-arctic-ice">10</div>
|
||||||
|
<div class="dc-row-4 darkspace font-night-rain">84</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">65821407321</div>
|
||||||
|
<div class="dc-row-2 font-alpha-blue">54018820533</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">27174523016</div>
|
||||||
|
<div class="dc-row-4">38954062564</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">999</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">202</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">574</div>
|
||||||
|
<div class="dc-row-4">293</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">3872</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">1105</div>
|
||||||
|
<div class="dc-row-3">1106</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">7411</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<!--
|
||||||
|
*** MAIN NAVIGATION BUTTONS ***
|
||||||
|
Replace the hashtag '#' with a real URL (or not).
|
||||||
|
If you don't want sound effects, replace the <button> element with a basic <a> tag shown here in this comment:
|
||||||
|
<a href="#">01</a>
|
||||||
|
<a href="#">02</a>
|
||||||
|
<a href="#">03</a>
|
||||||
|
<a href="#">04</a>
|
||||||
|
-->
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', 'crew.html')">01</button>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">02</button>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">03</button>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">04</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="bar-panel first-bar-panel">
|
||||||
|
<div class="bar-1"> </div>
|
||||||
|
<div class="bar-2"> </div>
|
||||||
|
<div class="bar-3"> </div>
|
||||||
|
<div class="bar-4"> </div>
|
||||||
|
<div class="bar-5"> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider">
|
||||||
|
<div class="block-left"> </div>
|
||||||
|
<div class="block-right">
|
||||||
|
<div class="block-row">
|
||||||
|
<div class="bar-11"> </div>
|
||||||
|
<div class="bar-12"> </div>
|
||||||
|
<div class="bar-13"> </div>
|
||||||
|
<div class="bar-14">
|
||||||
|
<div class="blockhead"> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="left-frame">
|
||||||
|
<!--
|
||||||
|
** SCROLL TO TOP OF PAGE BUTTON **
|
||||||
|
This button is initially hidden, and is styled like a panel in the sidebar. It appears at the bottom of the page after vertical scrolling. If you don't want the sound effect, replace with this:
|
||||||
|
<button onclick="topFunction()" id="topBtn"><span class="hop">screen</span> top</button>
|
||||||
|
-->
|
||||||
|
<button onclick="topFunction(); playSoundAndRedirect('audio4', '#')" id="topBtn"><span
|
||||||
|
class="hop">screen</span> top</button>
|
||||||
|
<div>
|
||||||
|
<div class="panel-3">03<span class="hop">-111968</span></div>
|
||||||
|
<div class="panel-4">04<span class="hop">-041969</span></div>
|
||||||
|
<div class="panel-5">05<span class="hop">-1701D</span></div>
|
||||||
|
<div class="panel-6">06<span class="hop">-071984</span></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="panel-7">07<span class="hop">-081940</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-frame">
|
||||||
|
<div class="bar-panel">
|
||||||
|
<div class="bar-6"> </div>
|
||||||
|
<div class="bar-7"> </div>
|
||||||
|
<div class="bar-8"> </div>
|
||||||
|
<div class="bar-9"> </div>
|
||||||
|
<div class="bar-10"> </div>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>Crew Sheet</h1>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<!-- Your copyright information is only a suggestion and you can choose to delete it. -->
|
||||||
|
Content Copyright © 2025 ld.hedeler.com <br>
|
||||||
|
|
||||||
|
<!-- The following attribution must not be removed: -->
|
||||||
|
LCARS Inspired Website Template by <a href="https://www.thelcars.com">www.TheLCARS.com</a>.
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="assets/lcars.js"></script>
|
||||||
|
<div class="headtrim"> </div>
|
||||||
|
<div class="baseboard"> </div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
232
lcars_v2/frontend/index.html
Normal file
232
lcars_v2/frontend/index.html
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Lower Decks PADD</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="format-detection" content="date=no">
|
||||||
|
<link rel="stylesheet" type="text/css" href="../assets/lower-decks-padd.css">
|
||||||
|
<script type="speculationrules">
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- <audio id="audio1" src="assets/beep1.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio2" src="assets/beep2.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio3" src="assets/beep3.mp3" preload="auto"></audio>
|
||||||
|
<audio id="audio4" src="assets/beep4.mp3" preload="auto"></audio> -->
|
||||||
|
<div class="wrap-all">
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="left-frame-top">
|
||||||
|
<!--
|
||||||
|
*** LCARS PANEL BUTTON ***
|
||||||
|
Replace the hashtag '#' with a real URL (or not) in the following <button> tag. If you do not want a sound effect for this link, replace the <button> element with the following <div> + <a> elements:
|
||||||
|
|
||||||
|
<div class="panel-1">
|
||||||
|
<a href="#">LCARS</a>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<button class="panel-1-button">LCARS</button>
|
||||||
|
<div class="panel-2">02<span class="hop">-262000</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="right-frame-top">
|
||||||
|
<div class="banner">LCARS 57436.2</div>
|
||||||
|
<div class="data-cascade-button-group">
|
||||||
|
<div class="data-wrapper">
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">47</div>
|
||||||
|
<div class="dc-row-2">31</div>
|
||||||
|
<div class="dc-row-3">28</div>
|
||||||
|
<div class="dc-row-4">94</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">329</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">128</div>
|
||||||
|
<div class="dc-row-3">605</div>
|
||||||
|
<div class="dc-row-4">704</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-night-rain">39725514862</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">51320259663</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">21857221984</div>
|
||||||
|
<div class="dc-row-4">40372566301</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">56</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">04</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">40</div>
|
||||||
|
<div class="dc-row-4 font-night-rain">35</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">614</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">883</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">109</div>
|
||||||
|
<div class="dc-row-4">297</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 darkspace darkfont">000</div>
|
||||||
|
<div class="dc-row-2 darkspace font-alpha-blue">13</div>
|
||||||
|
<div class="dc-row-3 darkspace font-arctic-ice">05</div>
|
||||||
|
<div class="dc-row-4 darkspace font-night-rain">25</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">48</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">07</div>
|
||||||
|
<div class="dc-row-3">38</div>
|
||||||
|
<div class="dc-row-4">62</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">416</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">001</div>
|
||||||
|
<div class="dc-row-3">888</div>
|
||||||
|
<div class="dc-row-4">442</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-night-rain">86225514862</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">31042009183</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">74882306985</div>
|
||||||
|
<div class="dc-row-4">54048523421</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-alpha-blue">10</div>
|
||||||
|
<div class="dc-row-2">80</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">31</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">85</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-alpha-blue">87</div>
|
||||||
|
<div class="dc-row-2">71</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">40</div>
|
||||||
|
<div class="dc-row-4 font-night-rain">26</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">98</div>
|
||||||
|
<div class="dc-row-2">63</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">52</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">71</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">118</div>
|
||||||
|
<div class="dc-row-2">270</div>
|
||||||
|
<div class="dc-row-3">395</div>
|
||||||
|
<div class="dc-row-4">260</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">8675309</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">7952705</div>
|
||||||
|
<div class="dc-row-3">9282721</div>
|
||||||
|
<div class="dc-row-4">4981518</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 darkspace darkfont">000</div>
|
||||||
|
<div class="dc-row-2 darkspace font-alpha-blue">99</div>
|
||||||
|
<div class="dc-row-3 darkspace font-arctic-ice">10</div>
|
||||||
|
<div class="dc-row-4 darkspace font-night-rain">84</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">65821407321</div>
|
||||||
|
<div class="dc-row-2 font-alpha-blue">54018820533</div>
|
||||||
|
<div class="dc-row-3 font-night-rain">27174523016</div>
|
||||||
|
<div class="dc-row-4">38954062564</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1 font-arctic-ice">999</div>
|
||||||
|
<div class="dc-row-2 font-arctic-ice">202</div>
|
||||||
|
<div class="dc-row-3 font-alpha-blue">574</div>
|
||||||
|
<div class="dc-row-4">293</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-column">
|
||||||
|
<div class="dc-row-1">3872</div>
|
||||||
|
<div class="dc-row-2 font-night-rain">1105</div>
|
||||||
|
<div class="dc-row-3">1106</div>
|
||||||
|
<div class="dc-row-4 font-alpha-blue">7411</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<!--
|
||||||
|
*** MAIN NAVIGATION BUTTONS ***
|
||||||
|
Replace the hashtag '#' with a real URL (or not).
|
||||||
|
If you don't want sound effects, replace the <button> element with a basic <a> tag shown here in this comment:
|
||||||
|
<a href="#">01</a>
|
||||||
|
<a href="#">02</a>
|
||||||
|
<a href="#">03</a>
|
||||||
|
<a href="#">04</a>
|
||||||
|
-->
|
||||||
|
<a href="crew">01</a>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">02</button>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">03</button>
|
||||||
|
<button onclick="playSoundAndRedirect('audio2', '#')">04</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="bar-panel first-bar-panel">
|
||||||
|
<div class="bar-1"> </div>
|
||||||
|
<div class="bar-2"> </div>
|
||||||
|
<div class="bar-3"> </div>
|
||||||
|
<div class="bar-4"> </div>
|
||||||
|
<div class="bar-5"> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider">
|
||||||
|
<div class="block-left"> </div>
|
||||||
|
<div class="block-right">
|
||||||
|
<div class="block-row">
|
||||||
|
<div class="bar-11"> </div>
|
||||||
|
<div class="bar-12"> </div>
|
||||||
|
<div class="bar-13"> </div>
|
||||||
|
<div class="bar-14">
|
||||||
|
<div class="blockhead"> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="left-frame">
|
||||||
|
<!--
|
||||||
|
** SCROLL TO TOP OF PAGE BUTTON **
|
||||||
|
This button is initially hidden, and is styled like a panel in the sidebar. It appears at the bottom of the page after vertical scrolling. If you don't want the sound effect, replace with this:
|
||||||
|
<button onclick="topFunction()" id="topBtn"><span class="hop">screen</span> top</button>
|
||||||
|
-->
|
||||||
|
<button onclick="topFunction(); playSoundAndRedirect('audio4', '#')" id="topBtn"><span
|
||||||
|
class="hop">screen</span> top</button>
|
||||||
|
<div>
|
||||||
|
<div class="panel-3">03<span class="hop">-111968</span></div>
|
||||||
|
<div class="panel-4">04<span class="hop">-041969</span></div>
|
||||||
|
<div class="panel-5">05<span class="hop">-1701D</span></div>
|
||||||
|
<div class="panel-6">06<span class="hop">-071984</span></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="panel-7">07<span class="hop">-081940</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-frame">
|
||||||
|
<div class="bar-panel">
|
||||||
|
<div class="bar-6"> </div>
|
||||||
|
<div class="bar-7"> </div>
|
||||||
|
<div class="bar-8"> </div>
|
||||||
|
<div class="bar-9"> </div>
|
||||||
|
<div class="bar-10"> </div>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>Lower Decks PADD</h1>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<!-- Your copyright information is only a suggestion and you can choose to delete it. -->
|
||||||
|
Content Copyright © 2025 ld.hedeler.com <br>
|
||||||
|
|
||||||
|
<!-- The following attribution must not be removed: -->
|
||||||
|
LCARS Inspired Website Template by <a href="https://www.thelcars.com">www.TheLCARS.com</a>.
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="assets/lcars.js"></script>
|
||||||
|
<div class="headtrim"> </div>
|
||||||
|
<div class="baseboard"> </div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
39
lcars_v2/frontend/tinyserver.go
Normal file
39
lcars_v2/frontend/tinyserver.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Folder to serve
|
||||||
|
dir := "."
|
||||||
|
|
||||||
|
// File server handler
|
||||||
|
fs := http.FileServer(http.Dir(dir))
|
||||||
|
|
||||||
|
// Wrap the file server to add caching headers
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ext := strings.ToLower(filepath.Ext(r.URL.Path))
|
||||||
|
|
||||||
|
// Set long caching for static assets
|
||||||
|
switch ext {
|
||||||
|
case ".css", ".js", ".woff", ".woff2", ".ttf", ".eot", ".svg", ".png", ".jpg", ".jpeg", ".gif":
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||||
|
default:
|
||||||
|
// Optional: short caching for HTML so you always get latest page
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
addr := ":8080"
|
||||||
|
log.Printf("Serving %s on http://localhost%s\n", dir, addr)
|
||||||
|
err := http.ListenAndServe(addr, handler)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lcars_v2/go.mod
Normal file
18
lcars_v2/go.mod
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module ld
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require modernc.org/sqlite v1.39.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
modernc.org/libc v1.66.3 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
)
|
||||||
49
lcars_v2/go.sum
Normal file
49
lcars_v2/go.sum
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
||||||
|
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
|
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||||
|
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||||
|
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||||
|
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
94
lcars_v2/interval/interval.go
Normal file
94
lcars_v2/interval/interval.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package interval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MutexMap[K comparable, V any] struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
m map[K]V
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MutexMap[K, V]) Get(key K) (V, error) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
v, ok := m.m[key]
|
||||||
|
if !ok {
|
||||||
|
return v, errors.New("unknown key")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MutexMap[K, V]) Set(key K, value V) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
m.m[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MutexMap[K, V]) Delete(key K) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
delete(m.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stopChannels = MutexMap[int, chan bool]{
|
||||||
|
m: make(map[int]chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInterval schedules a repeating task to be executed at a specified interval.
|
||||||
|
func SetInterval(f func(), milliseconds int) (id int) {
|
||||||
|
for {
|
||||||
|
id = rand.Int()
|
||||||
|
if _, err := stopChannels.Get(id); err == nil {
|
||||||
|
continue // ID collision, keep looking for another unique random value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := make(chan bool)
|
||||||
|
stopChannels.Set(id, stop)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Duration(milliseconds) * time.Millisecond)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearInterval stops a scheduled interval identified by the specified interval ID.
|
||||||
|
func ClearInterval(id int) error {
|
||||||
|
stop, err := stopChannels.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stop <- true
|
||||||
|
stopChannels.Delete(id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout schedules a one-time task to be executed after a specified interval.
|
||||||
|
func SetTimeout(f func(), milliseconds int) {
|
||||||
|
timer := time.NewTimer(time.Duration(milliseconds) * time.Millisecond)
|
||||||
|
go func() {
|
||||||
|
<-timer.C
|
||||||
|
timer.Stop()
|
||||||
|
f()
|
||||||
|
}()
|
||||||
|
}
|
||||||
217
lcars_v2/main.go
Normal file
217
lcars_v2/main.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"ld/eventbus"
|
||||||
|
"ld/interval"
|
||||||
|
"ld/server"
|
||||||
|
"ld/sqlite"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exitCodeErr = 1
|
||||||
|
exitCodeInterrupt = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var AppRoot = "./" // path for supporting files that sit in app root folder in production
|
||||||
|
|
||||||
|
//go:embed frontend/*
|
||||||
|
var frontend embed.FS
|
||||||
|
|
||||||
|
//go:embed embed
|
||||||
|
var embedded embed.FS
|
||||||
|
|
||||||
|
// main specific variables
|
||||||
|
var ExecutableName string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Here")
|
||||||
|
err := run()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(exitCodeErr)
|
||||||
|
}
|
||||||
|
ExecutableName, err = getExecutableName()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(exitCodeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCrew()
|
||||||
|
|
||||||
|
// readMessages()
|
||||||
|
ebus := eventbus.NewEventBus()
|
||||||
|
|
||||||
|
interval.SetInterval(func() {
|
||||||
|
fmt.Println("------------------")
|
||||||
|
}, 10)
|
||||||
|
|
||||||
|
runEventbus(ebus)
|
||||||
|
|
||||||
|
run()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEventbus(ebus *eventbus.EventBus) {
|
||||||
|
|
||||||
|
// Create a new instance
|
||||||
|
eventChannel := eventbus.NewEventChannel()
|
||||||
|
|
||||||
|
// Subscribe to "foo:baz" - or use a wildcard like "foo:*"
|
||||||
|
ebus.SubscribeChannel("foo:baz", eventChannel)
|
||||||
|
ebus.SubscribeChannel("pups-klo", eventChannel)
|
||||||
|
ebus.SubscribeChannel("ömme*", eventChannel)
|
||||||
|
|
||||||
|
eventChannelTopic := ebus.Subscribe("ömmels")
|
||||||
|
|
||||||
|
// Subscribe with existing channel use
|
||||||
|
// eventbus.SubscribeChannel("foo:*", eventChannel)
|
||||||
|
|
||||||
|
// Wait for the incoming event on the channel
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
for evt := range eventChannel {
|
||||||
|
fmt.Println("FIRST", evt.Topic, evt.Data)
|
||||||
|
evt.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
for evt := range eventChannel {
|
||||||
|
fmt.Println("SECOND", evt.Topic, evt.Data)
|
||||||
|
evt.Done()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
for evt := range eventChannelTopic {
|
||||||
|
fmt.Println("ÖMMELS", evt.Topic, evt.Data)
|
||||||
|
evt.Done()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
|
||||||
|
ebus.Publish("foo:baz", eventbus.Rec{"value": i})
|
||||||
|
ebus.Publish("pups-klo", eventbus.Rec{"value": i})
|
||||||
|
ebus.Publish("pups-klo", eventbus.Rec{"value": i})
|
||||||
|
ebus.Publish("ömmels", eventbus.Rec{"value": i})
|
||||||
|
ebus.Publish("ömmels", eventbus.Rec{"value": i * 10})
|
||||||
|
ebus.Publish("ömmels", eventbus.Rec{"value": i * 100})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
|
||||||
|
// setting up logging to file
|
||||||
|
logFileName := AppRoot + ExecutableName + ".log"
|
||||||
|
logFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error opening file: %v", err)
|
||||||
|
os.Exit(exitCodeErr)
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
log.SetOutput(logFile)
|
||||||
|
|
||||||
|
stateDB, err := createStateDB(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create internal StateDB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setting up the server
|
||||||
|
server, err := server.New(
|
||||||
|
logFileName,
|
||||||
|
stateDB,
|
||||||
|
embedded,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("server-app not created - error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tasks.SetupTasks(server)
|
||||||
|
|
||||||
|
err = server.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("server not started - error: %v \n", err)
|
||||||
|
log.Fatalf("server not started - error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
readCrew(server)
|
||||||
|
readMessages(server)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// listen for os shutdown events, report them into log file and exit application
|
||||||
|
chanOS := make(chan os.Signal, 2)
|
||||||
|
signal.Notify(chanOS, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-chanOS
|
||||||
|
log.Println("shutting down request signal received")
|
||||||
|
server.Close()
|
||||||
|
os.Exit(exitCodeInterrupt)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// setting up the routes, hooking up API endpoints with backend functions
|
||||||
|
// routes.SetupRoutes(server)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// some internal functions
|
||||||
|
|
||||||
|
func getExecutableName() (string, error) {
|
||||||
|
name, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
name = filepath.Base(name)
|
||||||
|
return strings.TrimSuffix(name, filepath.Ext(name)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createStateDB(StateDBDelete bool) (*sqlite.Database, error) {
|
||||||
|
|
||||||
|
// fileName := fmt.Sprintf("state-%s.db", ulid.Make())
|
||||||
|
|
||||||
|
fileName := "state.db"
|
||||||
|
|
||||||
|
if StateDBDelete {
|
||||||
|
_, err := os.Stat(fileName)
|
||||||
|
if err == nil {
|
||||||
|
err := os.Remove(fileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("error deleting statedb-file:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sqlite.New(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := embedded.ReadFile("embed/create_state_db.sql")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.DB().Exec(string(query))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
2
lcars_v2/notes.txt
Normal file
2
lcars_v2/notes.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<a target="_blank" href="https://icons8.com/icon/21039/star-trek">Nächste-Generation Abzeichen</a> Icon von <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||||
|
<a href="https://www.flaticon.com/free-icons/geek" title="geek icons">Geek icons created by Pixel perfect - Flaticon</a>
|
||||||
105
lcars_v2/server/server.go
Normal file
105
lcars_v2/server/server.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2024 codeM GmbH
|
||||||
|
// Author: Thomas Hedeler
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"ld/eventbus"
|
||||||
|
"ld/interval"
|
||||||
|
"ld/sqlite"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
SQLiteVersion string
|
||||||
|
ServerInfo map[string]any
|
||||||
|
StateDB *sqlite.Database
|
||||||
|
Embedded embed.FS
|
||||||
|
LogFileName string
|
||||||
|
TokenDuration int // TODO einbauen
|
||||||
|
Secret []byte
|
||||||
|
Header string
|
||||||
|
intervalID int
|
||||||
|
Ebus *eventbus.EventBus
|
||||||
|
Tasks map[string]TaskFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
logfilename string,
|
||||||
|
StateDB *sqlite.Database,
|
||||||
|
embedded embed.FS,
|
||||||
|
|
||||||
|
) (*Server, error) {
|
||||||
|
|
||||||
|
// creating the server
|
||||||
|
return &Server{
|
||||||
|
LogFileName: logfilename,
|
||||||
|
StateDB: StateDB,
|
||||||
|
Embedded: embedded,
|
||||||
|
Tasks: make(map[string]func(s *Server) error),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
|
||||||
|
// query, err := s.Embedded.ReadFile("embed/win/server_info.sql")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// res, err := s.StundenDB.ReadRecords(string(query))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// s.ServerInfo = res[0]
|
||||||
|
|
||||||
|
// err = inits.LoadLogins(s.StundenDB, s.StateDB)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// err = inits.LoadTasks(s.StundenDB, s.StateDB)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // start the task engine
|
||||||
|
// if s.Production {
|
||||||
|
// s.intervalID = interval.SetInterval(s.interval, 60000) // check for executable tasks every 60 seconds
|
||||||
|
// } else {
|
||||||
|
// s.intervalID = interval.SetInterval(s.interval, 30000) // check for executable tasks every 30 seconds
|
||||||
|
// }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Close() error {
|
||||||
|
|
||||||
|
// stop the task engine
|
||||||
|
err := interval.ClearInterval(s.intervalID)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.StateDB.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// func to dispatch routes to all parts of the application:
|
||||||
|
// they receive references to the server and the current fiber context via closures
|
||||||
|
// this way all functions have access to server properties and can handle the
|
||||||
|
// incoming requests themselves.
|
||||||
|
|
||||||
|
// type HandlerFunc = func(s *Server, c *fiber.Ctx) error
|
||||||
|
|
||||||
|
// func (s *Server) Handler(handler HandlerFunc) func(c *fiber.Ctx) error {
|
||||||
|
// return func(c *fiber.Ctx) error {
|
||||||
|
// return handler(s, c)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// signature for internal tasks
|
||||||
|
type TaskFunc = func(s *Server) error
|
||||||
118
lcars_v2/server/taskengine.go
Normal file
118
lcars_v2/server/taskengine.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this function schedules the tasks and will be called periodically, see server.Start()
|
||||||
|
func (s *Server) interval() {
|
||||||
|
|
||||||
|
// read scheduled task list from stateDB
|
||||||
|
// check for next executable task:
|
||||||
|
// - if there is one or more tasks ready for execution then select one of them.
|
||||||
|
// - if there is a selected task, update its next execution field and execute the task
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
// fmt.Println("Recovered from panic in worker:", r)
|
||||||
|
log.Printf("recovered from panic in taskengine: %v ", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tasks, err := s.StateDB.ReadRecords("SELECT * FROM tasks ORDER by next_execution limit 1;")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error in taskengine: %s ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tasks) < 1 {
|
||||||
|
log.Printf("error in taskengine: %s ", "found no task for execution")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := tasks[0] // pick the one task with the smallest next execution time, see previous sql statement
|
||||||
|
|
||||||
|
task_name, haveTask := task["task_name"].(string)
|
||||||
|
if !haveTask {
|
||||||
|
log.Printf("error in taskengine: task %s is of wrong type", task["task_name"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nextExecution := task["next_execution"].(int64)
|
||||||
|
startTime := task["start_time"].(int64)
|
||||||
|
execInterval := task["interval"].(int64)
|
||||||
|
nowSeconds := time.Now().Unix()
|
||||||
|
|
||||||
|
if nowSeconds < nextExecution { // task execution is not yet due
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate next execution time
|
||||||
|
for nextExecution = startTime; nowSeconds > nextExecution; nextExecution += execInterval {
|
||||||
|
// add as many intervals to the starttime until the next execution lies in the future
|
||||||
|
}
|
||||||
|
|
||||||
|
task["start_time"] = task["next_execution"]
|
||||||
|
task["next_execution"] = nextExecution
|
||||||
|
|
||||||
|
/*
|
||||||
|
no_executions INTEGER, -- how often executed
|
||||||
|
duration INTEGER, -- duration of the last exec in ms
|
||||||
|
no_errors INTEGER, -- error count
|
||||||
|
last_error_text TEXT,
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
if count, ok := task["no_executions"].(int64); ok {
|
||||||
|
task["no_executions"] = count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// update next_execution in state database
|
||||||
|
_, err = s.StateDB.UpsertRecord("tasks", "task_id", task)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error in taskengine: cannot update task record - before execution %s ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task_func, haveTask := s.Tasks[task_name] // select the function with the matching name
|
||||||
|
|
||||||
|
if !haveTask {
|
||||||
|
log.Printf("error in taskengine: task %s is not defined", task_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if haveTask {
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
// if !s.Production {
|
||||||
|
// fmt.Println("Taskengine: executing task:", task_name, start)
|
||||||
|
// }
|
||||||
|
err = task_func(s) // finally execute the task; attention: a task that panics will kill the server!
|
||||||
|
|
||||||
|
task["duration"] = int(time.Since(start).Milliseconds())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("taskengine: execution task: %s failed with error: %s ", task_name, err)
|
||||||
|
task["last_error_text"] = err.Error()
|
||||||
|
if count, ok := task["no_errors"].(int64); ok {
|
||||||
|
task["no_errors"] = count + 1
|
||||||
|
}
|
||||||
|
// if !s.Production {
|
||||||
|
// fmt.Println("Taskengine: failed task:", task_name, err)
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// if !s.Production {
|
||||||
|
// fmt.Println("Taskengine: successfully completed task:", task_name, time.Now())
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.StateDB.UpsertRecord("tasks", "task_id", task)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error in taskengine: cannot update task record - after execution %s ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
428
lcars_v2/sqlite/database.go
Normal file
428
lcars_v2/sqlite/database.go
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
package sqlite // name the package as you see fit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the data type to exchange data with the database
|
||||||
|
type Record = map[string]any
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
databaseName string
|
||||||
|
database *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
tx *sql.Tx
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action func(tx *sql.Tx) error
|
||||||
|
|
||||||
|
func New(DBName string) (*Database, error) {
|
||||||
|
return &Database{databaseName: DBName}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Close() error {
|
||||||
|
return d.database.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// provides access to the internal database object
|
||||||
|
func (d *Database) DB() *sql.DB {
|
||||||
|
return d.database
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Name() string {
|
||||||
|
return d.databaseName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Open() (err error) {
|
||||||
|
d.database, err = openSqliteDB(d.databaseName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) OpenInMemory() (err error) {
|
||||||
|
d.database, err = sql.Open("sqlite", ":memory:")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func openSqliteDB(databasefilename string) (*sql.DB, error) {
|
||||||
|
|
||||||
|
_, err := os.Stat(databasefilename)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return createDB(databasefilename)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sql.Open("sqlite", databasefilename)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDB(dbfileName string) (*sql.DB, error) {
|
||||||
|
|
||||||
|
query := `
|
||||||
|
PRAGMA page_size = 4096;
|
||||||
|
PRAGMA synchronous = off;
|
||||||
|
PRAGMA foreign_keys = off;
|
||||||
|
PRAGMA journal_mode = WAL;
|
||||||
|
PRAGMA user_version = 1;
|
||||||
|
`
|
||||||
|
db, err := sql.Open("sqlite", dbfileName)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = db.Exec(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) TableList() (result []Record, err error) {
|
||||||
|
return d.ReadRecords("select name from sqlite_master where type='table';")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) ReadTable(tablename string) (result []Record, err error) {
|
||||||
|
|
||||||
|
return d.ReadRecords(fmt.Sprintf("select * from '%s';", tablename))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) ReadRecords(query string, args ...any) (result []Record, err error) {
|
||||||
|
|
||||||
|
rows, err := d.DB().Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return Rows2records(rows)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetRecord(tablename string, idfield string, key any) (result Record, err error) {
|
||||||
|
|
||||||
|
query := fmt.Sprintf("select * from %s where %s = ?;", tablename, idfield)
|
||||||
|
res, err := d.DB().Query(query, key)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
return Rows2record(res)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) UpsertRecord(tablename string, idfield string, record Record) (result Record, err error) {
|
||||||
|
|
||||||
|
return upsert(d.DB(), tablename, idfield, record)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) DeleteRecord(tablename string, idfield string, id any) (err error) {
|
||||||
|
|
||||||
|
return deleteRecord(d.DB(), tablename, idfield, id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// *sql.DB and *sql.Tx both have a method named 'Query',
|
||||||
|
// this way they can both be passed into upsert and deleteRecord function
|
||||||
|
type iquery interface {
|
||||||
|
Query(query string, args ...any) (*sql.Rows, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upsert(t iquery, tablename string, idfield string, record Record) (result Record, err error) {
|
||||||
|
|
||||||
|
fields := []string{}
|
||||||
|
data := []any{}
|
||||||
|
for k, v := range record {
|
||||||
|
fields = append(fields, k)
|
||||||
|
data = append(data, v)
|
||||||
|
}
|
||||||
|
query, err := buildUpsertCommand(tablename, idfield, fields)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
res, err := t.Query(query, data...) // res contains the full record - see SQLite: RETURNING *
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
return Rows2record(res)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRecord(t iquery, tablename string, idfield string, id any) (err error) {
|
||||||
|
|
||||||
|
query := fmt.Sprintf("DELETE FROM \"%s\" WHERE \"%s\" = ?;", tablename, idfield)
|
||||||
|
_, err = t.Query(query, id)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUpsertCommand(tablename string, idfield string, fields []string) (string, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.Grow(256 + len(fields)*20) // rough preallocation
|
||||||
|
|
||||||
|
// INSERT INTO
|
||||||
|
sb.WriteString(`INSERT INTO "`)
|
||||||
|
sb.WriteString(tablename)
|
||||||
|
sb.WriteString(`"(`)
|
||||||
|
for i, f := range fields {
|
||||||
|
sb.WriteString(` "`)
|
||||||
|
sb.WriteString(f)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
if i < len(fields)-1 {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(")\n\tVALUES(")
|
||||||
|
|
||||||
|
// VALUES
|
||||||
|
for i := 0; i < len(fields); i++ {
|
||||||
|
sb.WriteString(" ?")
|
||||||
|
sb.Write(strconv.AppendInt(nil, int64(i+1), 10))
|
||||||
|
if i < len(fields)-1 {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(")\n\tON CONFLICT(\"")
|
||||||
|
sb.WriteString(tablename)
|
||||||
|
sb.WriteString(`"."`)
|
||||||
|
sb.WriteString(idfield)
|
||||||
|
sb.WriteString("\")\n\tDO UPDATE SET ")
|
||||||
|
|
||||||
|
// UPDATE SET
|
||||||
|
for i, f := range fields {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(f)
|
||||||
|
sb.WriteString(`"= ?`)
|
||||||
|
sb.Write(strconv.AppendInt(nil, int64(i+1), 10))
|
||||||
|
if i < len(fields)-1 {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("\n\tRETURNING *;")
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func buildUpsertCommand(tablename string, idfield string, fields []string) (result string, err error) {
|
||||||
|
|
||||||
|
// pname := map[string]string{} // assign correct index for parameter name
|
||||||
|
// // parameter position, starts at 1 in sql! So it needs to be calculated by function pname inside template
|
||||||
|
|
||||||
|
// for i, k := range fields {
|
||||||
|
// pname[k] = strconv.Itoa(i + 1)
|
||||||
|
// }
|
||||||
|
// funcMap := template.FuncMap{
|
||||||
|
// "pname": func(fieldname string) string {
|
||||||
|
// return pname[fieldname]
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// tableDef := struct {
|
||||||
|
// Tablename string
|
||||||
|
// KeyField string
|
||||||
|
// LastField int
|
||||||
|
// FieldNames []string
|
||||||
|
// }{
|
||||||
|
// Tablename: tablename,
|
||||||
|
// KeyField: idfield,
|
||||||
|
// LastField: len(fields) - 1,
|
||||||
|
// FieldNames: fields,
|
||||||
|
// }
|
||||||
|
// var templString = `{{$last := .LastField}}INSERT INTO "{{ .Tablename }}"({{ range $i,$el := .FieldNames }} "{{$el}}"{{if ne $i $last}},{{end}}{{end}})
|
||||||
|
// VALUES({{ range $i,$el := .FieldNames }} ?{{pname $el}}{{if ne $i $last}},{{end}}{{end}})
|
||||||
|
// ON CONFLICT("{{ .Tablename }}"."{{.KeyField}}")
|
||||||
|
// DO UPDATE SET {{ range $i,$el := .FieldNames }}"{{$el}}"= ?{{pname $el}}{{if ne $i $last}},{{end}}{{end}}
|
||||||
|
// RETURNING *;`
|
||||||
|
|
||||||
|
// dbTempl, err := template.New("upsertDB").Funcs(funcMap).Parse(templString)
|
||||||
|
// if err != nil {
|
||||||
|
// return result, err
|
||||||
|
// }
|
||||||
|
// var templBytes bytes.Buffer
|
||||||
|
// err = dbTempl.Execute(&templBytes, tableDef)
|
||||||
|
// if err != nil {
|
||||||
|
// return result, err
|
||||||
|
// }
|
||||||
|
// return templBytes.String(), nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func Rows2record(rows *sql.Rows) (Record, error) {
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values := make([]any, len(columns))
|
||||||
|
valuePtrs := make([]any, len(columns))
|
||||||
|
for i := range values {
|
||||||
|
valuePtrs[i] = &values[i]
|
||||||
|
}
|
||||||
|
result := Record{}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(valuePtrs...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, col := range columns {
|
||||||
|
result[col] = values[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil, errors.New("no rows found")
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rows2records(rows *sql.Rows) ([]Record, error) {
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
recLength := len(columns)
|
||||||
|
results := []Record{}
|
||||||
|
for rows.Next() {
|
||||||
|
values := make([]any, recLength)
|
||||||
|
valuePtrs := make([]any, recLength)
|
||||||
|
for i := range values {
|
||||||
|
valuePtrs[i] = &values[i]
|
||||||
|
}
|
||||||
|
record := Record{}
|
||||||
|
if err := rows.Scan(valuePtrs...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, col := range columns {
|
||||||
|
record[col] = values[i]
|
||||||
|
}
|
||||||
|
results = append(results, record)
|
||||||
|
}
|
||||||
|
if len(results) == 0 {
|
||||||
|
return nil, errors.New("no rows found")
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Version() (string, error) {
|
||||||
|
result := ""
|
||||||
|
sqliteversion, err := d.ReadRecords("SELECT sqlite_version();")
|
||||||
|
if len(sqliteversion) == 1 {
|
||||||
|
result = sqliteversion[0]["sqlite_version()"].(string)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) UserVersion() (int64, error) {
|
||||||
|
|
||||||
|
var result int64
|
||||||
|
userversion, err := d.ReadRecords("PRAGMA user_version;")
|
||||||
|
if len(userversion) == 1 {
|
||||||
|
result = userversion[0]["user_version"].(int64)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) Begin() *Transaction {
|
||||||
|
tx, err := d.database.Begin()
|
||||||
|
return &Transaction{tx, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transaction) Next(action Action) *Transaction {
|
||||||
|
if t.err != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
t.err = action(t.tx)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transaction) End() error {
|
||||||
|
|
||||||
|
if t.err != nil {
|
||||||
|
err := t.tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
t.err = errors.Join(t.err, err)
|
||||||
|
}
|
||||||
|
return t.err
|
||||||
|
}
|
||||||
|
t.err = t.tx.Commit()
|
||||||
|
return t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transaction) GetRecord(tablename string, idfield string, key any, output Record) *Transaction {
|
||||||
|
|
||||||
|
if t.err != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
query := fmt.Sprintf("select * from %s where %s = ?;", tablename, idfield)
|
||||||
|
res, err := t.tx.Query(query, key)
|
||||||
|
if err != nil {
|
||||||
|
t.err = err
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
result, err := Rows2record(res)
|
||||||
|
if err != nil {
|
||||||
|
t.err = err
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
for k := range output {
|
||||||
|
delete(output, k)
|
||||||
|
}
|
||||||
|
for k, v := range result {
|
||||||
|
output[k] = v
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transaction) UpsertRecord(tablename string, idfield string, record Record, output Record) *Transaction {
|
||||||
|
|
||||||
|
if t.err != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
result, err := upsert(t.tx, tablename, idfield, record)
|
||||||
|
if err != nil {
|
||||||
|
t.err = err
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
for k := range output {
|
||||||
|
delete(output, k)
|
||||||
|
}
|
||||||
|
for k, v := range result {
|
||||||
|
output[k] = v
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transaction) DeleteRecord(tablename string, idfield string, id any) *Transaction {
|
||||||
|
|
||||||
|
if t.err != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
err := deleteRecord(t.tx, tablename, idfield, id)
|
||||||
|
if err != nil {
|
||||||
|
t.err = err
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a value of the provided type, if the field exist and if it can be cast into the provided type parameter
|
||||||
|
func Value[T any](rec Record, field string) (value T, ok bool) {
|
||||||
|
var v any
|
||||||
|
if v, ok = rec[field]; ok {
|
||||||
|
value, ok = v.(T)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't report an error if there are simply just 'no rows found'
|
||||||
|
func NoRowsOk(recs []Record, err error) ([]Record, error) {
|
||||||
|
if err != nil && err.Error() != "no rows found" {
|
||||||
|
return recs, err
|
||||||
|
}
|
||||||
|
return recs, nil
|
||||||
|
}
|
||||||
196
sqlite/countries.csv
Normal file
196
sqlite/countries.csv
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
german_name,population,capital,timezones
|
||||||
|
Afghanistan,40218234,Kabul,"[""UTC+04:30""]"
|
||||||
|
Albanien,2837743,Tirana,"[""UTC+01:00""]"
|
||||||
|
Algerien,44700000,Algiers,"[""UTC+01:00""]"
|
||||||
|
Andorra,77265,"Andorra la Vella","[""UTC+01:00""]"
|
||||||
|
Angola,32866268,Luanda,"[""UTC+01:00""]"
|
||||||
|
"Antigua und Barbuda",97928,"Saint John's","[""UTC-04:00""]"
|
||||||
|
Argentinien,45376763,"Buenos Aires","[""UTC-03:00""]"
|
||||||
|
Armenien,2963234,Yerevan,"[""UTC+04:00""]"
|
||||||
|
Aserbaidschan,10110116,Baku,"[""UTC+04:00""]"
|
||||||
|
Australien,25687041,Canberra,"[""UTC+05:00"",""UTC+06:30"",""UTC+07:00"",""UTC+08:00"",""UTC+09:30"",""UTC+10:00"",""UTC+10:30"",""UTC+11:30""]"
|
||||||
|
Bahamas,393248,Nassau,"[""UTC-05:00""]"
|
||||||
|
Bahrain,1701583,Manama,"[""UTC+03:00""]"
|
||||||
|
Bangladesch,164689383,Dhaka,"[""UTC+06:00""]"
|
||||||
|
Barbados,287371,Bridgetown,"[""UTC-04:00""]"
|
||||||
|
Belgien,11555997,Brussels,"[""UTC+01:00""]"
|
||||||
|
Belize,397621,Belmopan,"[""UTC-06:00""]"
|
||||||
|
Benin,12123198,Porto-Novo,"[""UTC+01:00""]"
|
||||||
|
Bhutan,771612,Thimphu,"[""UTC+06:00""]"
|
||||||
|
Bolivien,11673029,Sucre,"[""UTC-04:00""]"
|
||||||
|
"Bosnien und Herzegowina",3280815,Sarajevo,"[""UTC+01:00""]"
|
||||||
|
Botswana,2351625,Gaborone,"[""UTC+02:00""]"
|
||||||
|
Brasilien,212559409,"Brasília","[""UTC-05:00"",""UTC-04:00"",""UTC-03:00"",""UTC-02:00""]"
|
||||||
|
Brunei,437483,"Bandar Seri Begawan","[""UTC+08:00""]"
|
||||||
|
Bulgarien,6927288,Sofia,"[""UTC+02:00""]"
|
||||||
|
"Burkina Faso",20903278,Ouagadougou,"[""UTC""]"
|
||||||
|
Burundi,11890781,Gitega,"[""UTC+02:00""]"
|
||||||
|
Chile,19116209,Santiago,"[""UTC-06:00"",""UTC-04:00""]"
|
||||||
|
China,1402112000,Beijing,"[""UTC+08:00""]"
|
||||||
|
"Costa Rica",5094114,"San José","[""UTC-06:00""]"
|
||||||
|
Deutschland,83240525,Berlin,"[""UTC+01:00""]"
|
||||||
|
Dominica,71991,Roseau,"[""UTC-04:00""]"
|
||||||
|
"Dominikanische Republik",10847904,"Santo Domingo","[""UTC-04:00""]"
|
||||||
|
Dschibuti,988002,Djibouti,"[""UTC+03:00""]"
|
||||||
|
"Dänemark",5831404,Copenhagen,"[""UTC-04:00"",""UTC-03:00"",""UTC-01:00"",""UTC"",""UTC+01:00""]"
|
||||||
|
Ecuador,17643060,Quito,"[""UTC-06:00"",""UTC-05:00""]"
|
||||||
|
"El Salvador",6486201,"San Salvador","[""UTC-06:00""]"
|
||||||
|
"Elfenbeinküste",26378275,Yamoussoukro,"[""UTC""]"
|
||||||
|
Eritrea,5352000,Asmara,"[""UTC+03:00""]"
|
||||||
|
Estland,1331057,Tallinn,"[""UTC+02:00""]"
|
||||||
|
Fidschi,896444,Suva,"[""UTC+12:00""]"
|
||||||
|
Finnland,5530719,Helsinki,"[""UTC+02:00""]"
|
||||||
|
Frankreich,67391582,Paris,"[""UTC-10:00"",""UTC-09:30"",""UTC-09:00"",""UTC-08:00"",""UTC-04:00"",""UTC-03:00"",""UTC+01:00"",""UTC+02:00"",""UTC+03:00"",""UTC+04:00"",""UTC+05:00"",""UTC+10:00"",""UTC+11:00"",""UTC+12:00""]"
|
||||||
|
Gabun,2225728,Libreville,"[""UTC+01:00""]"
|
||||||
|
Gambia,2416664,Banjul,"[""UTC+00:00""]"
|
||||||
|
Georgien,3714000,Tbilisi,"[""UTC+04:00""]"
|
||||||
|
Ghana,31072945,Accra,"[""UTC""]"
|
||||||
|
Grenada,112519,"St. George's","[""UTC-04:00""]"
|
||||||
|
Griechenland,10715549,Athens,"[""UTC+02:00""]"
|
||||||
|
Guatemala,16858333,"Guatemala City","[""UTC-06:00""]"
|
||||||
|
Guinea,13132792,Conakry,"[""UTC""]"
|
||||||
|
Guinea-Bissau,1967998,Bissau,"[""UTC""]"
|
||||||
|
Guyana,786559,Georgetown,"[""UTC-04:00""]"
|
||||||
|
Haiti,11402533,Port-au-Prince,"[""UTC-05:00""]"
|
||||||
|
Honduras,9904608,Tegucigalpa,"[""UTC-06:00""]"
|
||||||
|
Indien,1380004385,"New Delhi","[""UTC+05:30""]"
|
||||||
|
Indonesien,273523621,Jakarta,"[""UTC+07:00"",""UTC+08:00"",""UTC+09:00""]"
|
||||||
|
Irak,40222503,Baghdad,"[""UTC+03:00""]"
|
||||||
|
Iran,83992953,Tehran,"[""UTC+03:30""]"
|
||||||
|
Irland,4994724,Dublin,"[""UTC""]"
|
||||||
|
Island,366425,Reykjavik,"[""UTC""]"
|
||||||
|
Israel,9216900,Jerusalem,"[""UTC+02:00""]"
|
||||||
|
Italien,59554023,Rome,"[""UTC+01:00""]"
|
||||||
|
Jamaika,2961161,Kingston,"[""UTC-05:00""]"
|
||||||
|
Japan,125836021,Tokyo,"[""UTC+09:00""]"
|
||||||
|
Jemen,29825968,"Sana'a","[""UTC+03:00""]"
|
||||||
|
Jordanien,10203140,Amman,"[""UTC+03:00""]"
|
||||||
|
Kambodscha,16718971,"Phnom Penh","[""UTC+07:00""]"
|
||||||
|
Kamerun,26545864,"Yaoundé","[""UTC+01:00""]"
|
||||||
|
Kanada,38005238,Ottawa,"[""UTC-08:00"",""UTC-07:00"",""UTC-06:00"",""UTC-05:00"",""UTC-04:00"",""UTC-03:30""]"
|
||||||
|
"Kap Verde",555988,Praia,"[""UTC-01:00""]"
|
||||||
|
Kasachstan,18754440,Astana,"[""UTC+05:00"",""UTC+06:00""]"
|
||||||
|
Katar,2881060,Doha,"[""UTC+03:00""]"
|
||||||
|
Kenia,53771300,Nairobi,"[""UTC+03:00""]"
|
||||||
|
Kirgisistan,6591600,Bishkek,"[""UTC+06:00""]"
|
||||||
|
Kiribati,119446,"South Tarawa","[""UTC+12:00"",""UTC+13:00"",""UTC+14:00""]"
|
||||||
|
Kolumbien,50882884,"Bogotá","[""UTC-05:00""]"
|
||||||
|
Komoren,869595,Moroni,"[""UTC+03:00""]"
|
||||||
|
Kongo,5657000,Brazzaville,"[""UTC+01:00""]"
|
||||||
|
"Kongo (Dem. Rep.)",108407721,Kinshasa,"[""UTC+01:00"",""UTC+02:00""]"
|
||||||
|
Kosovo,1775378,Pristina,"[""UTC+01:00""]"
|
||||||
|
Kroatien,4047200,Zagreb,"[""UTC+01:00""]"
|
||||||
|
Kuba,11326616,Havana,"[""UTC-05:00""]"
|
||||||
|
Kuwait,4270563,"Kuwait City","[""UTC+03:00""]"
|
||||||
|
Laos,7275556,Vientiane,"[""UTC+07:00""]"
|
||||||
|
Lesotho,2142252,Maseru,"[""UTC+02:00""]"
|
||||||
|
Lettland,1901548,Riga,"[""UTC+02:00""]"
|
||||||
|
Libanon,6825442,Beirut,"[""UTC+02:00""]"
|
||||||
|
Liberia,5057677,Monrovia,"[""UTC""]"
|
||||||
|
Libyen,6871287,Tripoli,"[""UTC+01:00""]"
|
||||||
|
Liechtenstein,38137,Vaduz,"[""UTC+01:00""]"
|
||||||
|
Litauen,2794700,Vilnius,"[""UTC+02:00""]"
|
||||||
|
Luxemburg,632275,Luxembourg,"[""UTC+01:00""]"
|
||||||
|
Madagaskar,27691019,Antananarivo,"[""UTC+03:00""]"
|
||||||
|
Malawi,19129955,Lilongwe,"[""UTC+02:00""]"
|
||||||
|
Malaysia,32365998,"Kuala Lumpur","[""UTC+08:00""]"
|
||||||
|
Malediven,540542,"Malé","[""UTC+05:00""]"
|
||||||
|
Mali,20250834,Bamako,"[""UTC""]"
|
||||||
|
Malta,525285,Valletta,"[""UTC+01:00""]"
|
||||||
|
Marokko,36910558,Rabat,"[""UTC""]"
|
||||||
|
Marshallinseln,59194,Majuro,"[""UTC+12:00""]"
|
||||||
|
Mauretanien,4649660,Nouakchott,"[""UTC""]"
|
||||||
|
Mauritius,1265740,"Port Louis","[""UTC+04:00""]"
|
||||||
|
Mexiko,128932753,"Mexico City","[""UTC-08:00"",""UTC-07:00"",""UTC-06:00""]"
|
||||||
|
Mikronesien,115021,Palikir,"[""UTC+10:00"",""UTC+11:00""]"
|
||||||
|
Moldawien,2617820,"Chișinău","[""UTC+02:00""]"
|
||||||
|
Monaco,39244,Monaco,"[""UTC+01:00""]"
|
||||||
|
Mongolei,3278292,"Ulan Bator","[""UTC+07:00"",""UTC+08:00""]"
|
||||||
|
Montenegro,621718,Podgorica,"[""UTC+01:00""]"
|
||||||
|
Mosambik,31255435,Maputo,"[""UTC+02:00""]"
|
||||||
|
Myanmar,54409794,Naypyidaw,"[""UTC+06:30""]"
|
||||||
|
Namibia,2540916,Windhoek,"[""UTC+01:00""]"
|
||||||
|
Nauru,10834,Yaren,"[""UTC+12:00""]"
|
||||||
|
Nepal,29136808,Kathmandu,"[""UTC+05:45""]"
|
||||||
|
Neuseeland,5084300,Wellington,"[""UTC-11:00"",""UTC-10:00"",""UTC+12:00"",""UTC+12:45"",""UTC+13:00""]"
|
||||||
|
Nicaragua,6624554,Managua,"[""UTC-06:00""]"
|
||||||
|
Niederlande,16655799,Amsterdam,"[""UTC+01:00""]"
|
||||||
|
Niger,24206636,Niamey,"[""UTC+01:00""]"
|
||||||
|
Nigeria,206139587,Abuja,"[""UTC+01:00""]"
|
||||||
|
Nordkorea,25778815,Pyongyang,"[""UTC+09:00""]"
|
||||||
|
Nordmazedonien,2077132,Skopje,"[""UTC+01:00""]"
|
||||||
|
Norwegen,5379475,Oslo,"[""UTC+01:00""]"
|
||||||
|
Oman,5106622,Muscat,"[""UTC+04:00""]"
|
||||||
|
Osttimor,1318442,Dili,"[""UTC+09:00""]"
|
||||||
|
Pakistan,220892331,Islamabad,"[""UTC+05:00""]"
|
||||||
|
Palau,18092,Ngerulmud,"[""UTC+09:00""]"
|
||||||
|
Panama,4314768,"Panama City","[""UTC-05:00""]"
|
||||||
|
Papua-Neuguinea,8947027,"Port Moresby","[""UTC+10:00""]"
|
||||||
|
Paraguay,7132530,"Asunción","[""UTC-04:00""]"
|
||||||
|
Peru,32971846,Lima,"[""UTC-05:00""]"
|
||||||
|
Philippinen,109581085,Manila,"[""UTC+08:00""]"
|
||||||
|
Polen,37950802,Warsaw,"[""UTC+01:00""]"
|
||||||
|
Portugal,10305564,Lisbon,"[""UTC-01:00"",""UTC""]"
|
||||||
|
Ruanda,12952209,Kigali,"[""UTC+02:00""]"
|
||||||
|
"Rumänien",19286123,Bucharest,"[""UTC+02:00""]"
|
||||||
|
Russland,144104080,Moscow,"[""UTC+03:00"",""UTC+04:00"",""UTC+06:00"",""UTC+07:00"",""UTC+08:00"",""UTC+09:00"",""UTC+10:00"",""UTC+11:00"",""UTC+12:00""]"
|
||||||
|
Salomonen,686878,Honiara,"[""UTC+11:00""]"
|
||||||
|
Sambia,18383956,Lusaka,"[""UTC+02:00""]"
|
||||||
|
Samoa,198410,Apia,"[""UTC+13:00""]"
|
||||||
|
"San Marino",33938,"City of San Marino","[""UTC+01:00""]"
|
||||||
|
Saudi-Arabien,34813867,Riyadh,"[""UTC+03:00""]"
|
||||||
|
Schweden,10353442,Stockholm,"[""UTC+01:00""]"
|
||||||
|
Schweiz,8654622,Bern,"[""UTC+01:00""]"
|
||||||
|
Senegal,16743930,Dakar,"[""UTC""]"
|
||||||
|
Serbien,6908224,Belgrade,"[""UTC+01:00""]"
|
||||||
|
Seychellen,98462,Victoria,"[""UTC+04:00""]"
|
||||||
|
"Sierra Leone",7976985,Freetown,"[""UTC""]"
|
||||||
|
Simbabwe,14862927,Harare,"[""UTC+02:00""]"
|
||||||
|
Singapur,5685807,Singapore,"[""UTC+08:00""]"
|
||||||
|
Slowakei,5458827,Bratislava,"[""UTC+01:00""]"
|
||||||
|
Slowenien,2100126,Ljubljana,"[""UTC+01:00""]"
|
||||||
|
Somalia,15893219,Mogadishu,"[""UTC+03:00""]"
|
||||||
|
Spanien,47351567,Madrid,"[""UTC"",""UTC+01:00""]"
|
||||||
|
"Sri Lanka",21919000,"Sri Jayawardenepura Kotte","[""UTC+05:30""]"
|
||||||
|
"St. Kitts und Nevis",53192,Basseterre,"[""UTC-04:00""]"
|
||||||
|
"St. Lucia",183629,Castries,"[""UTC-04:00""]"
|
||||||
|
"St. Vincent und die Grenadinen",110947,Kingstown,"[""UTC-04:00""]"
|
||||||
|
Sudan,43849269,Khartoum,"[""UTC+03:00""]"
|
||||||
|
Suriname,586634,Paramaribo,"[""UTC-03:00""]"
|
||||||
|
Swasiland,1160164,Mbabane,"[""UTC+02:00""]"
|
||||||
|
Syrien,17500657,Damascus,"[""UTC+02:00""]"
|
||||||
|
"São Tomé und Príncipe",219161,"São Tomé","[""UTC""]"
|
||||||
|
"Südafrika",59308690,Pretoria,"[""UTC+02:00""]"
|
||||||
|
"Südkorea",51780579,Seoul,"[""UTC+09:00""]"
|
||||||
|
"Südsudan",11193729,Juba,"[""UTC+03:00""]"
|
||||||
|
Tadschikistan,9537642,Dushanbe,"[""UTC+05:00""]"
|
||||||
|
Tansania,59734213,Dodoma,"[""UTC+03:00""]"
|
||||||
|
Thailand,69799978,Bangkok,"[""UTC+07:00""]"
|
||||||
|
Togo,8278737,"Lomé","[""UTC""]"
|
||||||
|
Tonga,105697,"Nuku'alofa","[""UTC+13:00""]"
|
||||||
|
"Trinidad und Tobago",1399491,"Port of Spain","[""UTC-04:00""]"
|
||||||
|
Tschad,16425859,"N'Djamena","[""UTC+01:00""]"
|
||||||
|
Tschechien,10698896,Prague,"[""UTC+01:00""]"
|
||||||
|
Tunesien,11818618,Tunis,"[""UTC+01:00""]"
|
||||||
|
Turkmenistan,6031187,Ashgabat,"[""UTC+05:00""]"
|
||||||
|
Tuvalu,11792,Funafuti,"[""UTC+12:00""]"
|
||||||
|
"Türkei",84339067,Ankara,"[""UTC+03:00""]"
|
||||||
|
Uganda,45741000,Kampala,"[""UTC+03:00""]"
|
||||||
|
Ukraine,44134693,Kyiv,"[""UTC+02:00""]"
|
||||||
|
Ungarn,9749763,Budapest,"[""UTC+01:00""]"
|
||||||
|
Uruguay,3473727,Montevideo,"[""UTC-03:00""]"
|
||||||
|
Usbekistan,34232050,Tashkent,"[""UTC+05:00""]"
|
||||||
|
Vanuatu,307150,"Port Vila","[""UTC+11:00""]"
|
||||||
|
Vatikanstadt,451,"Vatican City","[""UTC+01:00""]"
|
||||||
|
Venezuela,28435943,Caracas,"[""UTC-04:00""]"
|
||||||
|
"Vereinigte Arabische Emirate",9890400,"Abu Dhabi","[""UTC+04:00""]"
|
||||||
|
"Vereinigte Staaten",329484123,"Washington, D.C.","[""UTC-12:00"",""UTC-11:00"",""UTC-10:00"",""UTC-09:00"",""UTC-08:00"",""UTC-07:00"",""UTC-06:00"",""UTC-05:00"",""UTC-04:00"",""UTC+10:00"",""UTC+12:00""]"
|
||||||
|
"Vereinigtes Königreich",67215293,London,"[""UTC-08:00"",""UTC-05:00"",""UTC-04:00"",""UTC-03:00"",""UTC-02:00"",""UTC"",""UTC+01:00"",""UTC+02:00"",""UTC+06:00""]"
|
||||||
|
Vietnam,97338583,Hanoi,"[""UTC+07:00""]"
|
||||||
|
"Weißrussland",9398861,Minsk,"[""UTC+03:00""]"
|
||||||
|
"Zentralafrikanische Republik",4829764,Bangui,"[""UTC+01:00""]"
|
||||||
|
Zypern,1207361,Nicosia,"[""UTC+02:00""]"
|
||||||
|
"Ägypten",102334403,Cairo,"[""UTC+02:00""]"
|
||||||
|
"Äquatorialguinea",1402985,Malabo,"[""UTC+01:00""]"
|
||||||
|
"Äthiopien",114963583,"Addis Ababa","[""UTC+03:00""]"
|
||||||
|
"Österreich",8917205,Vienna,"[""UTC+01:00""]"
|
||||||
|
1
sqlite/countries.json
Normal file
1
sqlite/countries.json
Normal file
File diff suppressed because one or more lines are too long
15
sqlite/countries.sql
Normal file
15
sqlite/countries.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.headers on
|
||||||
|
.mode table
|
||||||
|
|
||||||
|
WITH raw AS (
|
||||||
|
SELECT
|
||||||
|
value AS country
|
||||||
|
FROM json_each(readfile('countries.json'))
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
json_extract(country, '$.translations.deu.common') AS german_name,
|
||||||
|
json_extract(country, '$.population') AS population,
|
||||||
|
json_extract(country, '$.capital[0]') AS capital,
|
||||||
|
json_extract(country, '$.timezones') AS timezones
|
||||||
|
FROM raw
|
||||||
|
ORDER BY german_name;
|
||||||
18
sqlite/countries_csv.sql
Normal file
18
sqlite/countries_csv.sql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.mode csv
|
||||||
|
.headers on
|
||||||
|
.output countries.csv
|
||||||
|
|
||||||
|
WITH raw AS (
|
||||||
|
SELECT
|
||||||
|
value AS country
|
||||||
|
FROM json_each(readfile('countries.json'))
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
json_extract(country, '$.translations.deu.common') AS german_name,
|
||||||
|
json_extract(country, '$.population') AS population,
|
||||||
|
json_extract(country, '$.capital[0]') AS capital,
|
||||||
|
json_extract(country, '$.timezones') AS timezones
|
||||||
|
FROM raw
|
||||||
|
ORDER BY german_name;
|
||||||
|
|
||||||
|
.output stdout
|
||||||
86
sqlite/countries_import.sh
Executable file
86
sqlite/countries_import.sh
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to import REST Countries JSON data into SQLite
|
||||||
|
# Usage: ./import_countries.sh countries.json
|
||||||
|
|
||||||
|
JSON_FILE="${1:-countries.json}"
|
||||||
|
DB_FILE="countries.db"
|
||||||
|
|
||||||
|
# Check if JSON file exists
|
||||||
|
if [ ! -f "$JSON_FILE" ]; then
|
||||||
|
echo "Error: $JSON_FILE not found!"
|
||||||
|
echo "Usage: $0 <path-to-countries.json>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating SQLite database: $DB_FILE"
|
||||||
|
|
||||||
|
# Remove existing database if present
|
||||||
|
rm -f "$DB_FILE"
|
||||||
|
|
||||||
|
# Create database and import JSON
|
||||||
|
sqlite3 "$DB_FILE" << 'EOF'
|
||||||
|
-- Create table with JSON column
|
||||||
|
CREATE TABLE countries (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
data JSON NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Enable JSON mode for import
|
||||||
|
.mode json
|
||||||
|
|
||||||
|
-- Read and insert JSON data
|
||||||
|
.import countries.json countries_temp
|
||||||
|
|
||||||
|
-- The import creates a temporary table, now we need to move data
|
||||||
|
-- Since the JSON file is an array, we need to handle it properly
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Alternative approach: Use jq to process JSON array and insert row by row
|
||||||
|
echo "Processing JSON data..."
|
||||||
|
|
||||||
|
sqlite3 "$DB_FILE" << 'EOF'
|
||||||
|
-- If direct import doesn't work, we'll insert via stdin
|
||||||
|
CREATE TABLE IF NOT EXISTS countries (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
data JSON NOT NULL
|
||||||
|
);
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Use jq to split array into individual objects and insert
|
||||||
|
jq -c '.[]' "$JSON_FILE" | while IFS= read -r country; do
|
||||||
|
sqlite3 "$DB_FILE" "INSERT INTO countries (data) VALUES (json('$country'));"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Import complete!"
|
||||||
|
echo "Database created: $DB_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "Running sample queries..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Sample queries
|
||||||
|
sqlite3 -column -header "$DB_FILE" << 'EOF'
|
||||||
|
-- Count total countries
|
||||||
|
SELECT COUNT(*) as total_countries FROM countries;
|
||||||
|
|
||||||
|
-- Get countries with German names, population, and timezones
|
||||||
|
SELECT
|
||||||
|
json_extract(data, '$.name.common') as country_name,
|
||||||
|
json_extract(data, '$.name.nativeName.deu.official') as german_name,
|
||||||
|
json_extract(data, '$.population') as population,
|
||||||
|
json_extract(data, '$.timezones') as timezones
|
||||||
|
FROM countries
|
||||||
|
WHERE json_extract(data, '$.name.nativeName.deu.official') IS NOT NULL
|
||||||
|
LIMIT 10;
|
||||||
|
|
||||||
|
-- Get all German translations (from translations field)
|
||||||
|
SELECT
|
||||||
|
json_extract(data, '$.name.common') as country_name,
|
||||||
|
json_extract(data, '$.translations.deu.official') as german_official,
|
||||||
|
json_extract(data, '$.translations.deu.common') as german_common
|
||||||
|
FROM countries
|
||||||
|
LIMIT 10;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Database ready! You can now query with: sqlite3 $DB_FILE"
|
||||||
137
sqlite/extract.sql
Normal file
137
sqlite/extract.sql
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
-- Attach a new database with the specified name
|
||||||
|
ATTACH DATABASE 'food.db' AS food;
|
||||||
|
ATTACH DATABASE 'import.db' AS src;
|
||||||
|
-- ATTACH DATABASE 'food10k.db.orig' AS src;
|
||||||
|
ATTACH DATABASE ':memory:' AS cache;
|
||||||
|
-- ATTACH DATABASE 'memory.db' AS cache;
|
||||||
|
|
||||||
|
SELECT time(), "Creating tables and temporary tables.";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.product (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
_id TEXT,
|
||||||
|
name TEXT,
|
||||||
|
created_t INTEGER,
|
||||||
|
last_modified_t INTEGER,
|
||||||
|
countries TEXT,
|
||||||
|
creator_id INTEGER,
|
||||||
|
brand_owner_id INTEGER,
|
||||||
|
nutrition_grades
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.creator (
|
||||||
|
creator_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cache.creator (
|
||||||
|
creator_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.brand_owner (
|
||||||
|
brand_owner_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
brand_owner TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.product_nutriment_temp (
|
||||||
|
product_id INTEGER,
|
||||||
|
nutriment_key TEXT,
|
||||||
|
nutriment_value ANY
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.product_nutriment (
|
||||||
|
product_id INTEGER,
|
||||||
|
nutriment_id INTEGER,
|
||||||
|
nutriment_value ANY
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS food.nutriment (
|
||||||
|
nutriment_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
nutriment TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cache.brand_owner (
|
||||||
|
brand_owner_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
brand_owner TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cache.product (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
_id TEXT,
|
||||||
|
name TEXT,
|
||||||
|
created_t INTEGER,
|
||||||
|
last_modified_t INTEGER,
|
||||||
|
countries TEXT,
|
||||||
|
creator TEXT,
|
||||||
|
brand_owner TEXT,
|
||||||
|
nutrition_grades
|
||||||
|
);
|
||||||
|
|
||||||
|
SELECT time(), "Reading and caching base data.";
|
||||||
|
|
||||||
|
INSERT INTO cache.product (id, _id, name, created_t, last_modified_t, countries, creator, brand_owner, nutrition_grades) SELECT ID,
|
||||||
|
json_extract(data, '$._id'), json_extract(data, '$.product_name'),
|
||||||
|
json_extract(data, '$.created_t'), json_extract(data, '$.last_modified_t'),
|
||||||
|
json_extract(data, '$.countries'), json_extract(data, '$.creator'), json_extract(data, '$.brand_owner'),
|
||||||
|
json_extract(data, '$.nutrition_grades')
|
||||||
|
from foodraw;
|
||||||
|
|
||||||
|
|
||||||
|
SELECT time(), "Inserting cached data into creator table.";
|
||||||
|
|
||||||
|
INSERT INTO food.creator (creator )SELECT DISTINCT creator from cache.product ORDER BY creator;
|
||||||
|
INSERT INTO cache.creator (creator )SELECT DISTINCT creator from cache.product ORDER BY creator;
|
||||||
|
|
||||||
|
SELECT time(), "Inserting cached data into brand_owner table.";
|
||||||
|
|
||||||
|
INSERT INTO food.brand_owner (brand_owner )SELECT DISTINCT brand_owner from cache.product ORDER BY brand_owner;
|
||||||
|
INSERT INTO cache.brand_owner (brand_owner )SELECT DISTINCT brand_owner from cache.product ORDER BY brand_owner;
|
||||||
|
|
||||||
|
|
||||||
|
SELECT time(), "Inserting cached data into product table.";
|
||||||
|
|
||||||
|
INSERT INTO food.product (id, _id, name, created_t, last_modified_t, countries, creator_id, brand_owner_id, nutrition_grades)
|
||||||
|
SELECT id, _id, name, created_t, last_modified_t, countries, creator.creator_id as creator_id, brand_owner.brand_owner_id as brand_owner_id, nutrition_grades
|
||||||
|
FROM cache.product
|
||||||
|
LEFT JOIN cache.creator on cache.product.creator = cache.creator.creator
|
||||||
|
LEFT JOIN cache.brand_owner on cache.product.brand_owner = cache.brand_owner.brand_owner
|
||||||
|
where cache.product.name is not null and cache.product.name != "";
|
||||||
|
|
||||||
|
CREATE VIEW food.product_view as
|
||||||
|
SELECT id, _id, name, created_t, last_modified_t, countries, nutrition_grades, brand_owner.brand_owner, creator.creator FROM product
|
||||||
|
LEFT JOIN brand_owner on product.brand_owner_id = brand_owner.brand_owner_id
|
||||||
|
LEFT JOIN creator on product.creator_id = creator.creator_id
|
||||||
|
;
|
||||||
|
|
||||||
|
SELECT time(), "Reading nutriments and writing product_nutriment_temp table.";
|
||||||
|
|
||||||
|
INSERT INTO food.product_nutriment_temp ( product_id, nutriment_key, nutriment_value) select foodraw.id, key, value
|
||||||
|
from foodraw, json_each( data ->> '$.nutriments' );
|
||||||
|
|
||||||
|
INSERT INTO food.nutriment (nutriment) SELECT DISTINCT nutriment_key from food.product_nutriment_temp order by nutriment_key;
|
||||||
|
|
||||||
|
SELECT time(), "Writing product_nutriment table.";
|
||||||
|
|
||||||
|
INSERT INTO food.product_nutriment (product_id, nutriment_id, nutriment_value)
|
||||||
|
SELECT product_id, nutriment_id, nutriment_value from product_nutriment_temp JOIN nutriment on product_nutriment_temp.nutriment_key = nutriment.nutriment
|
||||||
|
WHERE nutriment_value != ""
|
||||||
|
;
|
||||||
|
|
||||||
|
CREATE VIEW food.nutriment_view as
|
||||||
|
SELECT product_id, nutriment, nutriment_value from product_nutriment NATURAL JOIN nutriment;
|
||||||
|
|
||||||
|
DROP TABLE food.product_nutriment_temp;
|
||||||
|
|
||||||
|
CREATE INDEX product_nutriment_product_product_id on product_nutriment(product_id);
|
||||||
|
CREATE INDEX product_nutriment_product_nutriment_id on product_nutriment(nutriment_id);
|
||||||
|
CREATE INDEX product_creator_id on product(creator_id);
|
||||||
|
CREATE INDEX product_brandowner_id on product(brand_owner_id);
|
||||||
|
CREATE INDEX product_name on product(name);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DETACH DATABASE food;
|
||||||
|
DETACH DATABASE src;
|
||||||
|
DETACH DATABASE cache;
|
||||||
27
sqlite/import.sh
Executable file
27
sqlite/import.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Define the name of the SQLite commands file
|
||||||
|
Imp_SQL_FILE="import_json.sql" # sql file perform the raw import
|
||||||
|
Ext_SQL_FILE="extract.sql" # sql file to extract the data from the imported db
|
||||||
|
|
||||||
|
# Database file names:
|
||||||
|
IMP_DB_FILE="import.db"
|
||||||
|
TGT_DB_FILE="food.db"
|
||||||
|
MEMORY_DB_FILE="memory.db"
|
||||||
|
|
||||||
|
# cleanup from previous runs
|
||||||
|
[ -e $IMP_DB_FILE ] && echo "$(date +"%T")|deleting $IMP_DB_FILE file" && rm $IMP_DB_FILE
|
||||||
|
[ -e $TGT_DB_FILE ] && echo "$(date +"%T")|deleting $TGT_DB_FILE file"&& rm $TGT_DB_FILE
|
||||||
|
[ -e $MEMORY_DB_FILE ] && echo "$(date +"%T")|deleting $TGT_DB_FILE file"&& rm $MEMORY_DB_FILE
|
||||||
|
|
||||||
|
# Execute SQLite commands from the SQL file
|
||||||
|
echo "$(date +"%T")|importing json file started"
|
||||||
|
|
||||||
|
sqlite3 "$IMP_DB_FILE" < "$Imp_SQL_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
echo "$(date +"%T")|importing json file finished"
|
||||||
|
|
||||||
|
# Execute SQLite commands from the SQL file
|
||||||
|
sqlite3 < "$Ext_SQL_FILE"
|
||||||
|
|
||||||
|
echo "$(date +"%T")|creating $TGT_DB_FILE finished"
|
||||||
7
sqlite/import_json.sql
Normal file
7
sqlite/import_json.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS foodraw (
|
||||||
|
data JSON NOT NULL,
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
.import off1000lines.jsonl foodraw
|
||||||
|
|
||||||
1000
sqlite/off1000lines.jsonl
Normal file
1000
sqlite/off1000lines.jsonl
Normal file
File diff suppressed because one or more lines are too long
70
sqlite/sudoku.sql
Normal file
70
sqlite/sudoku.sql
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
WITH RECURSIVE
|
||||||
|
input(sud) AS (
|
||||||
|
VALUES('53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79')
|
||||||
|
),
|
||||||
|
|
||||||
|
digits(z, lp) AS (
|
||||||
|
VALUES('1', 1)
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(lp+1 AS TEXT), lp+1 FROM digits WHERE lp<9
|
||||||
|
),
|
||||||
|
|
||||||
|
x(s, ind) AS (
|
||||||
|
SELECT sud, instr(sud, '.') FROM input
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
substr(s, 1, ind-1) || z || substr(s, ind+1),
|
||||||
|
instr(substr(s, 1, ind-1) || z || substr(s, ind+1), '.')
|
||||||
|
FROM x, digits AS z
|
||||||
|
WHERE ind>0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM digits AS lp
|
||||||
|
WHERE z.z = substr(s, ((ind-1)/9)*9 + lp, 1)
|
||||||
|
OR z.z = substr(s, ((ind-1)%9) + (lp-1)*9 + 1, 1)
|
||||||
|
OR z.z = substr(s, (((ind-1)/3) % 3)*3
|
||||||
|
+ ((ind-1)/27)*27
|
||||||
|
+ lp + ((lp-1)/3)*6, 1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
solved(s) AS (
|
||||||
|
SELECT s FROM x WHERE ind=0
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Build 9 pretty rows for both input and solved using digits.lp = 1..9
|
||||||
|
grid_rows(name, rn, line) AS (
|
||||||
|
SELECT 'input', d.lp,
|
||||||
|
substr(sud, (d.lp-1)*9+1, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(sud, (d.lp-1)*9+4, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(sud, (d.lp-1)*9+7, 3)
|
||||||
|
FROM input CROSS JOIN digits d
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT 'solved', d.lp,
|
||||||
|
substr(s, (d.lp-1)*9+1, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(s, (d.lp-1)*9+4, 3)
|
||||||
|
|| ' | ' ||
|
||||||
|
substr(s, (d.lp-1)*9+7, 3)
|
||||||
|
FROM solved CROSS JOIN digits d
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Insert horizontal separators after rows 3 and 6 by computing an ordering key
|
||||||
|
grid_with_sep AS (
|
||||||
|
SELECT name, rn*2-1 AS ord, line FROM grid_rows
|
||||||
|
UNION ALL
|
||||||
|
SELECT name, rn*2 AS ord, '----+-----+----' FROM grid_rows WHERE rn IN (3,6)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Single final UNIONed selection, ordered by group + position
|
||||||
|
SELECT line FROM (
|
||||||
|
SELECT 1 AS grp, ord AS ord, line FROM grid_with_sep WHERE name='input'
|
||||||
|
UNION ALL
|
||||||
|
SELECT 2 AS grp, 0 AS ord, '' AS line
|
||||||
|
UNION ALL
|
||||||
|
SELECT 3 AS grp, ord AS ord, line FROM grid_with_sep WHERE name='solved'
|
||||||
|
) ORDER BY grp, ord;
|
||||||
Loading…
x
Reference in New Issue
Block a user