Skip to content

Commit

Permalink
Merge pull request #1 from ddanilyuk/feature/campus
Browse files Browse the repository at this point in the history
feature/campus
  • Loading branch information
ddanilyuk authored Jun 4, 2022
2 parents ab0af8c + 65aaada commit 8af96df
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 28 deletions.
107 changes: 107 additions & 0 deletions Sources/App/Controllers/CampusController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// CampusController.swift
//
//
// Created by Denys Danyliuk on 02.06.2022.
//

import Vapor
import KPIHubParser
import Routes

struct StudySheetResponse: Content {
let studySheet: [StudySheetItem]
}

struct StudySheetItem: Content {
let lesson: StudySheetLesson
let activities: [StudySheetActivity]
}


final class CampusController {

func userInfo(
request: Request,
loginQuery: CampusLoginQuery
) async throws -> UserInfo {
let oauthResponse = try await oauth(request: request, loginQuery: loginQuery)
let campusAPICredentials = try oauthResponse.content.decode(CampusAPICredentials.self)

let accountInfoResponse: ClientResponse = try await request.client.get(
"https://api.campus.kpi.ua/Account/Info",
beforeSend: { clientRequest in
let auth = BearerAuthorization(token: campusAPICredentials.accessToken)
clientRequest.headers.bearerAuthorization = auth
}
)
return try accountInfoResponse.content.decode(UserInfo.self)
}

func studySheet(
request: Request,
loginQuery: CampusLoginQuery
) async throws -> StudySheetResponse {

let oauthResponse = try await oauth(request: request, loginQuery: loginQuery)
let campusAPICredentials = try oauthResponse.content.decode(CampusAPICredentials.self)

let authPHPResponse: ClientResponse = try await request.client.get(
"https://campus.kpi.ua/auth.php",
beforeSend: { clientRequest in
clientRequest.headers.cookie = oauthResponse.headers.setCookie
}
)

let studySheetResponse: ClientResponse = try await request.client.get(
"https://campus.kpi.ua/student/index.php?mode=studysheet",
beforeSend: { clientRequest in
let auth = BearerAuthorization(token: campusAPICredentials.accessToken)
clientRequest.headers.bearerAuthorization = auth
if let phpSessionId = authPHPResponse.headers.setCookie?.all["PHPSESSID"] {
clientRequest.headers.cookie = HTTPCookies(
dictionaryLiteral: ("PHPSESSID", phpSessionId)
)
}
}
)

let html = try (studySheetResponse.body).htmlString(encoding: .windowsCP1251)

let studySheetItems = try await StudySheetLessonsParser().parse(html)
.asyncMap { lesson -> StudySheetItem in
let response: ClientResponse = try await request.client.post(
"https://campus.kpi.ua\(lesson.link)",
beforeSend: { clientRequest in
let auth = BearerAuthorization(token: campusAPICredentials.accessToken)
clientRequest.headers.bearerAuthorization = auth
if let phpSessionId = authPHPResponse.headers.setCookie?.all["PHPSESSID"] {
clientRequest.headers.cookie = HTTPCookies(
dictionaryLiteral: ("PHPSESSID", phpSessionId)
)
}
}
)
let html = try (response.body).htmlString(encoding: .windowsCP1251)
let activities = try StudySheetActivitiesParser().parse(html)
return StudySheetItem(lesson: lesson, activities: activities)
}

return StudySheetResponse(studySheet: studySheetItems)
}

// MARK: - Helpers

func oauth(
request: Request,
loginQuery: CampusLoginQuery
) async throws -> ClientResponse {
try await request.client.post(
"https://api.campus.kpi.ua/oauth/token",
beforeSend: { clientRequest in
try clientRequest.query.encode(loginQuery)
}
)
}

}
30 changes: 19 additions & 11 deletions Sources/App/Controllers/GroupsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import KPIHubParser
import Fluent
import FluentPostgresDriver
import Foundation
import Routes

final class GroupsController {

Expand All @@ -21,6 +22,23 @@ final class GroupsController {

// MARK: - Requests

func allGroups(request: Request) async throws -> GroupsResponse {
let groupModels = try await GroupModel.query(on: request.db).all()
return GroupsResponse(numberOfGroups: groupModels.count, groups: groupModels)
}

func search(request: Request, searchQuery: GroupSearchQuery) async throws -> GroupModel {
// TODO: Handle multiple groups with one name
let groupModel = try await GroupModel.query(on: request.db)
.filter(\.$name == searchQuery.groupName)
.first()
if let groupModel = groupModel {
return groupModel
} else {
throw Abort(.notFound, reason: "Group not found")
}
}

func forceRefresh(request: Request) async throws -> GroupsResponse {
let groups = try await getNewGroups(
client: request.client,
Expand All @@ -35,11 +53,6 @@ final class GroupsController {
)
}

func allGroups(request: Request) async throws -> GroupsResponse {
let groupModels = try await GroupModel.query(on: request.db).all()
return GroupsResponse(numberOfGroups: groupModels.count, groups: groupModels)
}

// MARK: - Other methods

func getNewGroups(client: Client, logger: Logger) async throws -> [GroupModel] {
Expand Down Expand Up @@ -79,12 +92,7 @@ final class GroupsController {
)
numberOfParsedGroups += 1
logger.info("\(numberOfParsedGroups) \(response.headers)")
guard
var body = response.body,
let html = body.readString(length: body.readableBytes)
else {
throw Abort(.internalServerError)
}
let html = try (response.body).htmlString(encoding: .utf8)
return try GroupParser(groupName: groupName)
.parse(html)
.map { GroupModel(id: $0.id, name: $0.name) }
Expand Down
7 changes: 1 addition & 6 deletions Sources/App/Controllers/LessonsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ final class LessonsController {
let response = try await request.client.get(
"http://rozklad.kpi.ua/Schedules/ViewSchedule.aspx?g=\(uuid.uuidString)"
)
guard
var body = response.body,
let html = body.readString(length: body.readableBytes)
else {
throw Abort(.internalServerError)
}
let html = try (response.body).htmlString(encoding: .utf8)
let lessons = try LessonsParser().parse(html)
return LessonsResponse(id: uuid, lessons: lessons)
}
Expand Down
36 changes: 36 additions & 0 deletions Sources/App/Extensions/ByteBuffer+HTML.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// ByteBuffer+HTML.swift
//
//
// Created by Denys Danyliuk on 04.06.2022.
//

import Vapor
import Foundation

extension ByteBuffer {

public func htmlString(encoding: String.Encoding = .utf8) throws -> String {
let data = Data(buffer: self)
guard
let html = String(data: data, encoding: encoding)
else {
throw Abort(.internalServerError)
}
return html
}

}

extension Optional where Wrapped == ByteBuffer {

public func htmlString(encoding: String.Encoding = .utf8) throws -> String {
switch self {
case let .some(buffer):
return try buffer.htmlString(encoding: encoding)
case .none:
throw Abort(.internalServerError)
}
}

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

import Foundation

struct CampusAPICredentials: Equatable, Codable {

let accessToken: String
let sessionId: String

enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case sessionId
}
}
6 changes: 6 additions & 0 deletions Sources/App/Models/GroupModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ extension GroupModel: AsyncMigration {
}

}

// MARK: - GroupModel + Content

extension GroupModel: Content {

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

import Foundation
import Vapor

struct UserInfo: Codable, Equatable {

// MARK: - StudyGroup

struct InfoItem: Codable, Equatable {
let id: Int
let name: String
}

// MARK: - Profile

struct Profile: Codable, Equatable {
let id: Int
let profile: String
let subdivision: InfoItem
}

let modules: [String]
let position: [InfoItem]
let subdivision: [InfoItem]
let studyGroup: InfoItem
let sid: String
let email: String
let scientificInterest: String
let username: String
let tgAuthLinked: Bool
let profiles: [Profile]
let id: Int
let userIdentifier: String
let fullName: String
let photo: String
let credo: String
}

// MARK: UserInfo + Content

extension UserInfo: Content {

}
4 changes: 4 additions & 0 deletions Sources/App/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public func configure(_ app: Application) throws {

app.mount(rootRouter, use: rootHandler)

// MARK: - Client configuration

app.http.client.configuration.redirectConfiguration = .disallow

// MARK: - Cron

try app.cron.schedule(RefreshGroupsCron.self)
Expand Down
28 changes: 25 additions & 3 deletions Sources/App/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func apiHandler(
route: APIRoute
) async throws -> AsyncResponseEncodable {
switch route {
case let .campus(route):
return try await campusHandler(request: request, route: route)


case let .groups(route):
return try await groupsHandler(request: request, route: route)

Expand All @@ -32,6 +36,23 @@ func apiHandler(
}
}

// MARK: - campusHandler

func campusHandler(
request: Request,
route: CampusRoute
) async throws -> AsyncResponseEncodable {
switch route {
case let .userInfo(loginQuery):
let controller = CampusController()
return try await controller.userInfo(request: request, loginQuery: loginQuery)

case let .studySheet(loginQuery):
let controller = CampusController()
return try await controller.studySheet(request: request, loginQuery: loginQuery)
}
}

// MARK: - groupsHandler

func groupsHandler(
Expand All @@ -43,12 +64,13 @@ func groupsHandler(
let controller = GroupsController()
return try await controller.allGroups(request: request)

case let .search(searchQuery):
let controller = GroupsController()
return try await controller.search(request: request, searchQuery: searchQuery)

case .forceRefresh:
let controller = GroupsController()
return try await controller.forceRefresh(request: request)

case let .search(groupQuery):
return "\(route) | \(groupQuery)"
}
}

Expand Down
18 changes: 18 additions & 0 deletions Sources/KPIHubParser/Models/StudySheetActivity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// StudySheetActivity.swift
//
//
// Created by Denys Danyliuk on 03.06.2022.
//

import Foundation

public struct StudySheetActivity: Codable {

public let date: String
public let mark: String
public let type: String
public let teacher: String
public let note: String

}
18 changes: 18 additions & 0 deletions Sources/KPIHubParser/Models/StudySheetLesson.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// StudySheetLesson.swift
//
//
// Created by Denys Danyliuk on 03.06.2022.
//

import Foundation

public struct StudySheetLesson: Codable {

public let year: String
public let semester: String
public let link: String
public let name: String
public let teacher: String

}
Loading

0 comments on commit 8af96df

Please sign in to comment.