Skip to content

Commit

Permalink
Add V2 Rozklad API
Browse files Browse the repository at this point in the history
  • Loading branch information
ddanilyuk committed Jul 2, 2022
1 parent 520a85c commit abbccc9
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 32 deletions.
22 changes: 0 additions & 22 deletions Sources/App/Controllers/LessonsController.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// GroupsController.swift
// RozkladController.swift
//
//
// Created by Denys Danyliuk on 20.05.2022.
Expand All @@ -12,7 +12,8 @@ import FluentPostgresDriver
import Foundation
import Routes

final class GroupsController {
/// This client is base on parsing groups and lessons from rozklad.kpi.ua
final class RozkladController {

static let ukrainianAlphabet: [String] = [
"а", "б", "в", "г", "д", "е", "є", "ж", "з", "и", "і",
Expand Down Expand Up @@ -59,7 +60,7 @@ final class GroupsController {
var numberOfParsedGroups = 0

// Receiving groups names from first endpoint
let groupsNames = try await GroupsController.ukrainianAlphabet
let groupsNames = try await RozkladController.ukrainianAlphabet
.asyncMap { letter -> AllGroupsClientResponse in
let response: ClientResponse = try await client.post(
"http://rozklad.kpi.ua/Schedules/ScheduleGroupSelection.aspx/GetGroups",
Expand Down Expand Up @@ -101,6 +102,15 @@ final class GroupsController {
.uniqued()
}

func getLessons(for groupUUID: UUID, request: Request) async throws -> LessonsResponse {
let response = try await request.client.get(
"http://rozklad.kpi.ua/Schedules/ViewSchedule.aspx?g=\(groupUUID.uuidString)"
)
let html = try (response.body).htmlString(encoding: .utf8)
let lessons = try LessonsParser().parse(html)
return LessonsResponse(id: groupUUID, lessons: lessons)
}

func scheduleGroupSelectionParameters(with groupName: String) -> String {
"ctl00_ToolkitScriptManager_HiddenField=&__VIEWSTATE=%2FwEMDAwQAgAADgEMBQAMEAIAAA4BDAUDDBACAAAOAgwFBwwQAgwPAgEIQ3NzQ2xhc3MBD2J0biBidG4tcHJpbWFyeQEEXyFTQgUCAAAADAUNDBACAAAOAQwFAQwQAgAADgEMBQ0MEAIMDwEBBFRleHQBG9Cg0L7Qt9C60LvQsNC0INC30LDQvdGP0YLRjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALVdjzppTCyUtNVSyV7xykGQzHz2&__EVENTTARGET=&__EVENTARGUMENT=&ctl00%24MainContent%24ctl00%24txtboxGroup=\(groupName)&ctl00%24MainContent%24ctl00%24btnShowSchedule=%D0%A0%D0%BE%D0%B7%D0%BA%D0%BB%D0%B0%D0%B4%2B%D0%B7%D0%B0%D0%BD%D1%8F%D1%82%D1%8C&__EVENTVALIDATION=%2FwEdAAEAAAD%2F%2F%2F%2F%2FAQAAAAAAAAAPAQAAAAUAAAAIsA3rWl3AM%2B6E94I5Tu9cRJoVjv0LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHfLZVQO6kVoZVPGurJN4JJIAuaU&hiddenInputToUpdateATBuffer_CommonToolkitScripts=0"
}
Expand Down
51 changes: 51 additions & 0 deletions Sources/App/Controllers/RozkladControllerV2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// GroupsControllerV2.swift
//
//
// Created by Denys Danyliuk on 01.07.2022.
//

import Vapor
import Routes
import KPIHubParser
import Foundation

final class RozkladControllerV2 {

func allGroups(request: Request) async throws -> GroupsResponseV2 {
let response: ClientResponse = try await request.client.get(
"https://schedule.kpi.ua/api/schedule/groups"
)
let result = try response.content.decode(GroupModelV2ClientResponse.self)
return GroupsResponseV2(
numberOfGroups: result.data.count,
groups: result.data.sorted(by: {
$0.name.compare($1.name, locale: Locale(identifier: "uk")) == .orderedAscending
})
)
}

func search(request: Request, searchQuery: GroupSearchQuery) async throws -> GroupV2 {
let allGroups = try await allGroups(request: request)
let searchedGroup = allGroups.groups.first {
$0.name.lowercased().contains(searchQuery.groupName.lowercased())
}
if let searchedGroup = searchedGroup {
return searchedGroup
} else {
throw Abort(.notFound, reason: "Group not found")
}
}

func getLessons(for groupUUID: UUID, request: Request) async throws -> LessonsResponseV2 {
let response: ClientResponse = try await request.client.get(
"https://schedule.kpi.ua/api/schedule/lessons",
beforeSend: { request in
try request.query.encode(["groupId": groupUUID.uuidString])
}
)
let lessonsV2ClientResponse = try response.content.decode(LessonsV2ClientResponse.self)
return LessonsResponseV2(id: groupUUID, lessons: lessonsV2ClientResponse.lessonsV2())
}

}
2 changes: 1 addition & 1 deletion Sources/App/Cron/RefreshGroupsCron.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct RefreshGroupsCron: AsyncVaporCronSchedulable {

public static func task(on application: Application) async throws -> Void {
application.logger.info("\(Self.self) is running...")
let groupsController = GroupsController()
let groupsController = RozkladController()
let groups = try await groupsController.getNewGroups(
client: application.client,
logger: application.logger
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// GroupModelV2ClientResponse.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

struct GroupModelV2ClientResponse: Content {

var data: [GroupV2]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// LessonsV2ClientResponse.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

struct LessonsV2ClientResponse: Content {

let data: Data

struct Data: Content {
let scheduleFirstWeek: [Day]
let scheduleSecondWeek: [Day]
}

struct Day: Content {
let day: String
let pairs: [Pair]
}

struct Pair: Content {
let teacherName: String
let lecturerId: String
let type: String
let time: String
let name: String
let place: String
let tag: String
}

}

extension LessonsV2ClientResponse {

func lessonsV2() -> [LessonV2] {
let firstWeek = getWeekLessons(from: data.scheduleFirstWeek, week: .first)
let secondWeek = getWeekLessons(from: data.scheduleSecondWeek, week: .second)
return firstWeek + secondWeek
}

private func getWeekLessons(from days: [LessonsV2ClientResponse.Day], week: LessonV2.Week) -> [LessonV2] {
days
.enumerated()
.flatMap { index, day -> [LessonV2] in
day.pairs
.reduce(into: []) { partialResult, pair in
let firstIndex = partialResult.firstIndex(where: { $0.position.description.contains(pair.time) })
if let firstIndex = firstIndex {
var old = partialResult.remove(at: firstIndex)
if !old.names.contains(pair.name) {
old.names.append(pair.name)
}
if !(old.teachers?.contains(pair.teacherName) ?? false) {
old.teachers?.append(pair.teacherName)
}
if !(old.locations?.contains(pair.place) ?? false) {
old.locations?.append(pair.place)
}
partialResult.insert(old, at: firstIndex)

} else {
partialResult.append(
LessonV2(
names: [pair.name],
teachers: [pair.teacherName],
locations: [pair.place],
type: pair.type,
position: .init(pair.time),
day: LessonV2.Day(rawValue: index + 1) ?? .monday,
week: week
)
)
}
}
.sorted { lhs, rhs in
lhs.position.rawValue < rhs.position.rawValue
}
}
}
}
16 changes: 16 additions & 0 deletions Sources/App/Models/RozkladV2/GroupV2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// GroupV2.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

struct GroupV2: Content {

var id: UUID?
var name: String
var faculty: String?

}
15 changes: 15 additions & 0 deletions Sources/App/Models/RozkladV2/GroupsResponseV2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// GroupsResponseV2.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

struct GroupsResponseV2: Content {

let numberOfGroups: Int
let groups: [GroupV2]

}
103 changes: 103 additions & 0 deletions Sources/App/Models/RozkladV2/LessonV2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// LessonV2.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

public struct LessonV2: Equatable {

// MARK: - Position

public enum Position: Int, Codable, CaseIterable, Equatable {
case first = 1
case second
case third
case fourth
case fifth
case sixth

init(_ string: String) {
switch string {
case "8.30":
self = .first

case "10.25":
self = .second

case "12.20":
self = .third

case "14.15":
self = .fourth

case "16.10":
self = .fifth

case "18.30", "18.05":
self = .sixth

default:
self = .first
}
}

var description: [String] {
switch self {
case .first:
return ["8.30"]

case .second:
return ["10.25"]

case .third:
return ["12.20"]

case .fourth:
return ["14.15"]

case .fifth:
return ["16.10"]

case .sixth:
return ["18.30", "18.05"]
}
}
}

// MARK: - Day

public enum Day: Int, Codable, CaseIterable, Equatable {
case monday = 1
case tuesday
case wednesday
case thursday
case friday
case saturday
}

// MARK: - Week

public enum Week: Int, Codable, Equatable {
case first = 1
case second
}

public var names: [String]
public var teachers: [String]?
public var locations: [String]?
public var type: String

public let position: Position
public let day: Day
public let week: Week

}

// MARK: - Codable

extension LessonV2: Codable {

}
21 changes: 21 additions & 0 deletions Sources/App/Models/RozkladV2/LessonsResponseV2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Denys Danyliuk on 02.07.2022.
//

import Vapor

struct LessonsResponseV2 {
var id: UUID
let lessons: [LessonV2]
}

extension LessonsResponseV2: Codable {

}

extension LessonsResponseV2: Content {

}
3 changes: 3 additions & 0 deletions Sources/App/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import VaporCron

public func configure(_ app: Application) throws {

app.http.server.configuration.hostname = "0.0.0.0"
app.http.server.configuration.port = 8080

app.logger.notice("env \(app.environment)")
app.logger.notice("host \(Environment.get("DATABASE_HOST") ?? "no value")")
app.logger.notice("port \(Environment.get("DATABASE_PORT") ?? "no value")")
Expand Down
Loading

0 comments on commit abbccc9

Please sign in to comment.