DatabaseService.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. using Npgsql;
  2. using TransportMauiApp.Models;
  3. namespace TransportMauiApp.Services;
  4. public sealed class DatabaseService
  5. {
  6. private const string ConnectionString =
  7. "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=krylatov_go;Password=o#GQ0&n$t47;Search Path=krylatov_go;Include Error Detail=true";
  8. private readonly NpgsqlDataSource _dataSource = NpgsqlDataSource.Create(ConnectionString);
  9. public async Task<UserSession?> AuthenticateAsync(string login, string password)
  10. {
  11. const string sql = """
  12. select u.user_id, u.login, u.password, u.role_id, coalesce(r.role_name, '')
  13. from users u
  14. left join role r on r.role_id = u.role_id
  15. where u.login = @login
  16. """;
  17. await using var connection = await _dataSource.OpenConnectionAsync();
  18. await using var command = new NpgsqlCommand(sql, connection);
  19. command.Parameters.AddWithValue("login", login);
  20. await using var reader = await command.ExecuteReaderAsync();
  21. if (!await reader.ReadAsync())
  22. {
  23. return null;
  24. }
  25. var passwordHash = reader.GetString(2);
  26. var passwordIsValid = false;
  27. try
  28. {
  29. passwordIsValid = BCrypt.Net.BCrypt.Verify(password, passwordHash);
  30. }
  31. catch (Exception)
  32. {
  33. passwordIsValid = false;
  34. }
  35. if (!passwordIsValid)
  36. {
  37. return null;
  38. }
  39. return new UserSession
  40. {
  41. UserId = reader.GetInt32(0),
  42. Login = reader.GetString(1),
  43. RoleId = reader.IsDBNull(3) ? null : reader.GetInt32(3),
  44. RoleName = reader.GetString(4)
  45. };
  46. }
  47. public Task<int> GetDriversCountAsync()
  48. {
  49. return GetCountAsync("select count(*) from driver");
  50. }
  51. public Task<int> GetTransportsCountAsync()
  52. {
  53. return GetCountAsync("select count(*) from transport");
  54. }
  55. public Task<int> GetRoutesCountAsync()
  56. {
  57. return GetCountAsync("select count(*) from route");
  58. }
  59. public Task<int> GetFlightsCountAsync()
  60. {
  61. return GetCountAsync("select count(*) from transport_flight");
  62. }
  63. public Task<int> GetFlightInfoCountAsync()
  64. {
  65. return GetCountAsync("select count(*) from flight_info_view");
  66. }
  67. public async Task<IReadOnlyList<Driver>> GetDriversAsync()
  68. {
  69. const string sql = """
  70. select driver_id, name, surname, patronimic
  71. from driver
  72. order by driver_id
  73. """;
  74. var result = new List<Driver>();
  75. await using var connection = await _dataSource.OpenConnectionAsync();
  76. await using var command = new NpgsqlCommand(sql, connection);
  77. await using var reader = await command.ExecuteReaderAsync();
  78. while (await reader.ReadAsync())
  79. {
  80. result.Add(new Driver
  81. {
  82. DriverId = reader.GetInt32(0),
  83. Name = reader.GetString(1),
  84. Surname = reader.GetString(2),
  85. Patronimic = reader.GetString(3)
  86. });
  87. }
  88. return result;
  89. }
  90. public async Task<int> AddDriverAsync(Driver driver)
  91. {
  92. const string sql = """
  93. insert into driver (name, surname, patronimic)
  94. values (@name, @surname, @patronimic)
  95. returning driver_id
  96. """;
  97. await using var connection = await _dataSource.OpenConnectionAsync();
  98. await SyncSerialSequenceAsync(connection, "driver");
  99. await using var command = new NpgsqlCommand(sql, connection);
  100. command.Parameters.AddWithValue("name", driver.Name);
  101. command.Parameters.AddWithValue("surname", driver.Surname);
  102. command.Parameters.AddWithValue("patronimic", driver.Patronimic);
  103. return Convert.ToInt32(await command.ExecuteScalarAsync());
  104. }
  105. public async Task UpdateDriverAsync(Driver driver)
  106. {
  107. const string sql = """
  108. update driver
  109. set name = @name, surname = @surname, patronimic = @patronimic
  110. where driver_id = @driver_id
  111. """;
  112. await using var connection = await _dataSource.OpenConnectionAsync();
  113. await using var command = new NpgsqlCommand(sql, connection);
  114. command.Parameters.AddWithValue("driver_id", driver.DriverId);
  115. command.Parameters.AddWithValue("name", driver.Name);
  116. command.Parameters.AddWithValue("surname", driver.Surname);
  117. command.Parameters.AddWithValue("patronimic", driver.Patronimic);
  118. await command.ExecuteNonQueryAsync();
  119. }
  120. public async Task DeleteDriverAsync(int driverId)
  121. {
  122. await using var connection = await _dataSource.OpenConnectionAsync();
  123. await using var transaction = await connection.BeginTransactionAsync();
  124. try
  125. {
  126. await ExecuteNonQueryAsync(
  127. connection,
  128. transaction,
  129. "update transport set driver_id = null where driver_id = @driver_id",
  130. command => command.Parameters.AddWithValue("driver_id", driverId));
  131. await ExecuteNonQueryAsync(
  132. connection,
  133. transaction,
  134. "delete from driver where driver_id = @driver_id",
  135. command => command.Parameters.AddWithValue("driver_id", driverId));
  136. await transaction.CommitAsync();
  137. }
  138. catch
  139. {
  140. await transaction.RollbackAsync();
  141. throw;
  142. }
  143. }
  144. public async Task<IReadOnlyList<AppTransport>> GetTransportsAsync()
  145. {
  146. const string sql = """
  147. select t.transport_id, t.branch, t.model, t.license_plate_number, t.driver_id,
  148. coalesce(d.surname || ' ' || d.name || ' ' || d.patronimic, '')
  149. from transport t
  150. left join driver d on d.driver_id = t.driver_id
  151. order by t.transport_id
  152. """;
  153. var result = new List<AppTransport>();
  154. await using var connection = await _dataSource.OpenConnectionAsync();
  155. await using var command = new NpgsqlCommand(sql, connection);
  156. await using var reader = await command.ExecuteReaderAsync();
  157. while (await reader.ReadAsync())
  158. {
  159. result.Add(new AppTransport
  160. {
  161. TransportId = reader.GetInt32(0),
  162. Branch = reader.GetString(1),
  163. Model = reader.GetString(2),
  164. LicensePlateNumber = reader.GetString(3),
  165. DriverId = reader.IsDBNull(4) ? null : reader.GetInt32(4),
  166. DriverFullName = reader.GetString(5)
  167. });
  168. }
  169. return result;
  170. }
  171. public async Task<int> AddTransportAsync(AppTransport transport)
  172. {
  173. const string sql = """
  174. insert into transport (branch, model, license_plate_number, driver_id)
  175. values (@branch, @model, @license_plate_number, @driver_id)
  176. returning transport_id
  177. """;
  178. await using var connection = await _dataSource.OpenConnectionAsync();
  179. await SyncSerialSequenceAsync(connection, "transport");
  180. await using var command = new NpgsqlCommand(sql, connection);
  181. AddTransportParameters(command, transport);
  182. return Convert.ToInt32(await command.ExecuteScalarAsync());
  183. }
  184. public async Task UpdateTransportAsync(AppTransport transport)
  185. {
  186. const string sql = """
  187. update transport
  188. set branch = @branch,
  189. model = @model,
  190. license_plate_number = @license_plate_number,
  191. driver_id = @driver_id
  192. where transport_id = @transport_id
  193. """;
  194. await using var connection = await _dataSource.OpenConnectionAsync();
  195. await using var command = new NpgsqlCommand(sql, connection);
  196. command.Parameters.AddWithValue("transport_id", transport.TransportId);
  197. AddTransportParameters(command, transport);
  198. await command.ExecuteNonQueryAsync();
  199. }
  200. public async Task DeleteTransportAsync(int transportId)
  201. {
  202. await using var connection = await _dataSource.OpenConnectionAsync();
  203. await using var transaction = await connection.BeginTransactionAsync();
  204. try
  205. {
  206. await ExecuteNonQueryAsync(
  207. connection,
  208. transaction,
  209. "update transport_flight set transport_id = null where transport_id = @transport_id",
  210. command => command.Parameters.AddWithValue("transport_id", transportId));
  211. await ExecuteNonQueryAsync(
  212. connection,
  213. transaction,
  214. "delete from transport where transport_id = @transport_id",
  215. command => command.Parameters.AddWithValue("transport_id", transportId));
  216. await transaction.CommitAsync();
  217. }
  218. catch
  219. {
  220. await transaction.RollbackAsync();
  221. throw;
  222. }
  223. }
  224. public async Task AssignDriverToTransportAsync(int transportId, int driverId)
  225. {
  226. const string sql = "call assign_driver_to_transport(@p_transport_id, @p_driver_id)";
  227. await ExecuteNonQueryAsync(sql, command =>
  228. {
  229. command.Parameters.AddWithValue("p_transport_id", transportId);
  230. command.Parameters.AddWithValue("p_driver_id", driverId);
  231. });
  232. }
  233. public async Task<IReadOnlyList<TransitRoute>> GetRoutesAsync()
  234. {
  235. const string sql = """
  236. select route_id, route_name
  237. from route
  238. order by route_id
  239. """;
  240. var result = new List<TransitRoute>();
  241. await using var connection = await _dataSource.OpenConnectionAsync();
  242. await using var command = new NpgsqlCommand(sql, connection);
  243. await using var reader = await command.ExecuteReaderAsync();
  244. while (await reader.ReadAsync())
  245. {
  246. result.Add(new TransitRoute
  247. {
  248. RouteId = reader.GetInt32(0),
  249. RouteName = reader.GetString(1)
  250. });
  251. }
  252. return result;
  253. }
  254. public async Task<int> AddRouteAsync(TransitRoute route)
  255. {
  256. const string sql = """
  257. insert into route (route_name)
  258. values (@route_name)
  259. returning route_id
  260. """;
  261. await using var connection = await _dataSource.OpenConnectionAsync();
  262. await SyncSerialSequenceAsync(connection, "route");
  263. await using var command = new NpgsqlCommand(sql, connection);
  264. command.Parameters.AddWithValue("route_name", route.RouteName);
  265. return Convert.ToInt32(await command.ExecuteScalarAsync());
  266. }
  267. public async Task UpdateRouteAsync(TransitRoute route)
  268. {
  269. const string sql = "update route set route_name = @route_name where route_id = @route_id";
  270. await ExecuteNonQueryAsync(sql, command =>
  271. {
  272. command.Parameters.AddWithValue("route_id", route.RouteId);
  273. command.Parameters.AddWithValue("route_name", route.RouteName);
  274. });
  275. }
  276. public async Task DeleteRouteAsync(int routeId)
  277. {
  278. await using var connection = await _dataSource.OpenConnectionAsync();
  279. await using var transaction = await connection.BeginTransactionAsync();
  280. try
  281. {
  282. await ExecuteNonQueryAsync(
  283. connection,
  284. transaction,
  285. "update transport_flight set route_id = null where route_id = @route_id",
  286. command => command.Parameters.AddWithValue("route_id", routeId));
  287. await ExecuteNonQueryAsync(
  288. connection,
  289. transaction,
  290. "update route_point set route_id = null where route_id = @route_id",
  291. command => command.Parameters.AddWithValue("route_id", routeId));
  292. await ExecuteNonQueryAsync(
  293. connection,
  294. transaction,
  295. "delete from route where route_id = @route_id",
  296. command => command.Parameters.AddWithValue("route_id", routeId));
  297. await transaction.CommitAsync();
  298. }
  299. catch
  300. {
  301. await transaction.RollbackAsync();
  302. throw;
  303. }
  304. }
  305. public async Task<IReadOnlyList<TransportFlight>> GetFlightsAsync()
  306. {
  307. const string sql = """
  308. select tf.flight_id, tf.duration, tf.transport_id, tf.route_id,
  309. coalesce(t.model || ' ' || t.license_plate_number, ''),
  310. coalesce(r.route_name, '')
  311. from transport_flight tf
  312. left join transport t on t.transport_id = tf.transport_id
  313. left join route r on r.route_id = tf.route_id
  314. order by tf.flight_id
  315. """;
  316. var result = new List<TransportFlight>();
  317. await using var connection = await _dataSource.OpenConnectionAsync();
  318. await using var command = new NpgsqlCommand(sql, connection);
  319. await using var reader = await command.ExecuteReaderAsync();
  320. while (await reader.ReadAsync())
  321. {
  322. result.Add(ReadFlight(reader));
  323. }
  324. return result;
  325. }
  326. public async Task<int> AddFlightAsync(TransportFlight flight)
  327. {
  328. const string sql = """
  329. insert into transport_flight (duration, transport_id, route_id)
  330. values (@duration, @transport_id, @route_id)
  331. returning flight_id
  332. """;
  333. await using var connection = await _dataSource.OpenConnectionAsync();
  334. await SyncSerialSequenceAsync(connection, "transport_flight");
  335. await using var command = new NpgsqlCommand(sql, connection);
  336. AddFlightParameters(command, flight);
  337. return Convert.ToInt32(await command.ExecuteScalarAsync());
  338. }
  339. public async Task UpdateFlightAsync(TransportFlight flight)
  340. {
  341. const string sql = """
  342. update transport_flight
  343. set duration = @duration,
  344. transport_id = @transport_id,
  345. route_id = @route_id
  346. where flight_id = @flight_id
  347. """;
  348. await using var connection = await _dataSource.OpenConnectionAsync();
  349. await using var command = new NpgsqlCommand(sql, connection);
  350. command.Parameters.AddWithValue("flight_id", flight.FlightId);
  351. AddFlightParameters(command, flight);
  352. await command.ExecuteNonQueryAsync();
  353. }
  354. public async Task DeleteFlightAsync(int flightId)
  355. {
  356. const string sql = "delete from transport_flight where flight_id = @flight_id";
  357. await ExecuteNonQueryAsync(sql, command => command.Parameters.AddWithValue("flight_id", flightId));
  358. }
  359. public async Task<IReadOnlyList<FlightInfo>> GetFlightInfoAsync()
  360. {
  361. const string sql = """
  362. select flight_id, duration, route_name, model, license_plate_number, driver_fio
  363. from flight_info_view
  364. order by flight_id
  365. """;
  366. var result = new List<FlightInfo>();
  367. await using var connection = await _dataSource.OpenConnectionAsync();
  368. await using var command = new NpgsqlCommand(sql, connection);
  369. await using var reader = await command.ExecuteReaderAsync();
  370. while (await reader.ReadAsync())
  371. {
  372. result.Add(new FlightInfo
  373. {
  374. FlightId = reader.GetInt32(0),
  375. Duration = reader.GetTimeSpan(1),
  376. RouteName = reader.GetString(2),
  377. Model = reader.GetString(3),
  378. LicensePlateNumber = reader.GetString(4),
  379. DriverFio = reader.GetString(5)
  380. });
  381. }
  382. return result;
  383. }
  384. public async Task<IReadOnlyList<FlightInfo>> GetFlightsByRouteAsync(int routeId)
  385. {
  386. const string sql = """
  387. select f.flight_id, f.duration, r.route_name, f.model, f.license_plate_number, f.driver_full_name
  388. from get_flights_by_route(@p_route_id) f
  389. join route r on r.route_id = @p_route_id
  390. order by f.flight_id
  391. """;
  392. var result = new List<FlightInfo>();
  393. await using var connection = await _dataSource.OpenConnectionAsync();
  394. await using var command = new NpgsqlCommand(sql, connection);
  395. command.Parameters.AddWithValue("p_route_id", routeId);
  396. await using var reader = await command.ExecuteReaderAsync();
  397. while (await reader.ReadAsync())
  398. {
  399. result.Add(new FlightInfo
  400. {
  401. FlightId = reader.GetInt32(0),
  402. Duration = reader.GetTimeSpan(1),
  403. RouteName = reader.GetString(2),
  404. Model = reader.GetString(3),
  405. LicensePlateNumber = reader.GetString(4),
  406. DriverFio = reader.GetString(5)
  407. });
  408. }
  409. return result;
  410. }
  411. private static TransportFlight ReadFlight(NpgsqlDataReader reader)
  412. {
  413. return new TransportFlight
  414. {
  415. FlightId = reader.GetInt32(0),
  416. Duration = reader.GetTimeSpan(1),
  417. TransportId = reader.IsDBNull(2) ? null : reader.GetInt32(2),
  418. RouteId = reader.IsDBNull(3) ? null : reader.GetInt32(3),
  419. TransportName = reader.GetString(4),
  420. RouteName = reader.GetString(5)
  421. };
  422. }
  423. private static void AddTransportParameters(NpgsqlCommand command, AppTransport transport)
  424. {
  425. command.Parameters.AddWithValue("branch", transport.Branch);
  426. command.Parameters.AddWithValue("model", transport.Model);
  427. command.Parameters.AddWithValue("license_plate_number", transport.LicensePlateNumber);
  428. command.Parameters.AddWithValue("driver_id", (object?)transport.DriverId ?? DBNull.Value);
  429. }
  430. private static void AddFlightParameters(NpgsqlCommand command, TransportFlight flight)
  431. {
  432. command.Parameters.AddWithValue("duration", flight.Duration);
  433. command.Parameters.AddWithValue("transport_id", (object?)flight.TransportId ?? DBNull.Value);
  434. command.Parameters.AddWithValue("route_id", (object?)flight.RouteId ?? DBNull.Value);
  435. }
  436. private static async Task SyncSerialSequenceAsync(NpgsqlConnection connection, string tableName)
  437. {
  438. var sql = tableName switch
  439. {
  440. "driver" => """
  441. select setval(
  442. pg_get_serial_sequence('driver', 'driver_id'),
  443. coalesce((select max(driver_id) from driver), 0) + 1,
  444. false
  445. )
  446. """,
  447. "transport" => """
  448. select setval(
  449. pg_get_serial_sequence('transport', 'transport_id'),
  450. coalesce((select max(transport_id) from transport), 0) + 1,
  451. false
  452. )
  453. """,
  454. "route" => """
  455. select setval(
  456. pg_get_serial_sequence('route', 'route_id'),
  457. coalesce((select max(route_id) from route), 0) + 1,
  458. false
  459. )
  460. """,
  461. "transport_flight" => """
  462. select setval(
  463. pg_get_serial_sequence('transport_flight', 'flight_id'),
  464. coalesce((select max(flight_id) from transport_flight), 0) + 1,
  465. false
  466. )
  467. """,
  468. _ => throw new ArgumentOutOfRangeException(nameof(tableName), tableName, "Sequence sync is not configured for this table.")
  469. };
  470. await using var command = new NpgsqlCommand(sql, connection);
  471. await command.ExecuteNonQueryAsync();
  472. }
  473. private async Task ExecuteNonQueryAsync(string sql, Action<NpgsqlCommand> configure)
  474. {
  475. await using var connection = await _dataSource.OpenConnectionAsync();
  476. await using var command = new NpgsqlCommand(sql, connection);
  477. configure(command);
  478. await command.ExecuteNonQueryAsync();
  479. }
  480. private async Task<int> GetCountAsync(string sql)
  481. {
  482. await using var connection = await _dataSource.OpenConnectionAsync();
  483. await using var command = new NpgsqlCommand(sql, connection);
  484. return Convert.ToInt32(await command.ExecuteScalarAsync());
  485. }
  486. private static async Task ExecuteNonQueryAsync(
  487. NpgsqlConnection connection,
  488. NpgsqlTransaction transaction,
  489. string sql,
  490. Action<NpgsqlCommand> configure)
  491. {
  492. await using var command = new NpgsqlCommand(sql, connection, transaction);
  493. configure(command);
  494. await command.ExecuteNonQueryAsync();
  495. }
  496. }