diff --git a/Quickfeed.xcodeproj/project.pbxproj b/Quickfeed.xcodeproj/project.pbxproj index b54bc20..95e628e 100644 --- a/Quickfeed.xcodeproj/project.pbxproj +++ b/Quickfeed.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ D0F6A14E2609F54300C1D825 /* UserEnrollments.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F6A14D2609F54300C1D825 /* UserEnrollments.swift */; }; D0F9FC6325BEB9A90080A1F2 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D0F9FC6225BEB9A90080A1F2 /* GRPC */; }; D0F9FC6525BEB9A90080A1F2 /* CGRPCZlib in Frameworks */ = {isa = PBXBuildFile; productRef = D0F9FC6425BEB9A90080A1F2 /* CGRPCZlib */; }; + FA12A8AC268DD53F00B83F4F /* Conf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA12A8AB268DD53F00B83F4F /* Conf.swift */; }; FA17B95525EF7D6F008BB07F /* SubmissionReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA17B95425EF7D6F008BB07F /* SubmissionReview.swift */; }; FA17B95D25EF81F1008BB07F /* TranslateStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA17B95C25EF81F1008BB07F /* TranslateStatus.swift */; }; FA17B96225EF90E6008BB07F /* SubmissionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA17B96125EF90E6008BB07F /* SubmissionInfo.swift */; }; @@ -76,6 +77,7 @@ FA8EA6A126039B0D00A10351 /* GroupList.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8EA6A026039B0D00A10351 /* GroupList.swift */; }; FA9B950A26835A64003F115B /* ag.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9BCF4268358F5009B1E9B /* ag.grpc.swift */; }; FA9B950B26835A77003F115B /* ag.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9BCF3268358F5009B1E9B /* ag.pb.swift */; }; + FA9B950D2685D0A6003F115B /* AuthWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA9B950C2685D0A6003F115B /* AuthWebView.swift */; }; FA9DC66A2644548D007F1A24 /* ReviewListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA9DC6692644548D007F1A24 /* ReviewListHeader.swift */; }; FAA5DA36262EC643009DAEDE /* GradingBenchmarkHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA5DA35262EC643009DAEDE /* GradingBenchmarkHeader.swift */; }; FAA798B5262D6C82005E2713 /* AssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA798B4262D6C82005E2713 /* AssignmentView.swift */; }; @@ -129,6 +131,8 @@ D0F6A13B2609DD5000C1D825 /* RemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImage.swift; sourceTree = ""; }; D0F6A1452609F25500C1D825 /* UserInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInformation.swift; sourceTree = ""; }; D0F6A14D2609F54300C1D825 /* UserEnrollments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEnrollments.swift; sourceTree = ""; }; + FA12A8A9268DC82F00B83F4F /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + FA12A8AB268DD53F00B83F4F /* Conf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conf.swift; sourceTree = ""; }; FA17B95425EF7D6F008BB07F /* SubmissionReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmissionReview.swift; sourceTree = ""; }; FA17B95C25EF81F1008BB07F /* TranslateStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateStatus.swift; sourceTree = ""; }; FA17B96125EF90E6008BB07F /* SubmissionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmissionInfo.swift; sourceTree = ""; }; @@ -168,6 +172,7 @@ FA85791225BEB4EA00223DB9 /* Quickfeed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Quickfeed.entitlements; sourceTree = ""; }; FA8EA69B26038F5D00A10351 /* AddGroupForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupForm.swift; sourceTree = ""; }; FA8EA6A026039B0D00A10351 /* GroupList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupList.swift; sourceTree = ""; }; + FA9B950C2685D0A6003F115B /* AuthWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthWebView.swift; sourceTree = ""; }; FA9DC6692644548D007F1A24 /* ReviewListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewListHeader.swift; sourceTree = ""; }; FAA5DA35262EC643009DAEDE /* GradingBenchmarkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradingBenchmarkHeader.swift; sourceTree = ""; }; FAA798B4262D6C82005E2713 /* AssignmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssignmentView.swift; sourceTree = ""; }; @@ -389,6 +394,8 @@ FA85791125BEB4EA00223DB9 /* Info.plist */, FA85791225BEB4EA00223DB9 /* Quickfeed.entitlements */, FA85790E25BEB4EA00223DB9 /* Preview Content */, + FA12A8A9268DC82F00B83F4F /* Config.xcconfig */, + FA12A8AB268DD53F00B83F4F /* Conf.swift */, ); path = Quickfeed; sourceTree = ""; @@ -416,6 +423,7 @@ FA85790A25BEB4E900223DB9 /* ContentView.swift */, D08D62952611D72A00343250 /* LogIn.swift */, D035C75725E92795005AC2BA /* NavigatorView.swift */, + FA9B950C2685D0A6003F115B /* AuthWebView.swift */, D0F6A1432609F20A00C1D825 /* User */, D04331D726028EAB0034C4B9 /* Admin */, D04B46E125D12E9D00AC8926 /* Student */, @@ -600,6 +608,7 @@ FAA798B5262D6C82005E2713 /* AssignmentView.swift in Sources */, FA17B97525EFD40B008BB07F /* MemberListHeader.swift in Sources */, FA9B950B26835A77003F115B /* ag.pb.swift in Sources */, + FA12A8AC268DD53F00B83F4F /* Conf.swift in Sources */, FA17B96225EF90E6008BB07F /* SubmissionInfo.swift in Sources */, FAA8333025E4F1BD008C05F4 /* TeacherViewModel.swift in Sources */, FA9DC66A2644548D007F1A24 /* ReviewListHeader.swift in Sources */, @@ -645,6 +654,7 @@ FA5A40E62609DCA700A20A13 /* SelectedMembers.swift in Sources */, FAA8330525E3E073008C05F4 /* MemberListItem.swift in Sources */, FA29717825D311B5000C99A1 /* SearchFieldController.swift in Sources */, + FA9B950D2685D0A6003F115B /* AuthWebView.swift in Sources */, FAA8333E25E519E2008C05F4 /* ResultGrid.swift in Sources */, FA5FBDE225E7E63E007EC0E3 /* ServerProvider.swift in Sources */, D04B46EC25D1301200AC8926 /* LabSection.swift in Sources */, @@ -685,6 +695,7 @@ /* Begin XCBuildConfiguration section */ FA85792925BEB4EA00223DB9 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FA12A8A9268DC82F00B83F4F /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -746,6 +757,7 @@ }; FA85792A25BEB4EA00223DB9 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FA12A8A9268DC82F00B83F4F /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/Quickfeed/Conf.swift b/Quickfeed/Conf.swift new file mode 100644 index 0000000..d195aa9 --- /dev/null +++ b/Quickfeed/Conf.swift @@ -0,0 +1,38 @@ +// +// Conf.swift +// Quickfeed +// +// Created by Oskar Gjølga on 01/07/2021. +// + +import Foundation + +enum Configuration { + enum Error: Swift.Error { + case missingKey, invalidValue + } + + static func value(for key: String) throws -> T where T: LosslessStringConvertible { + guard let object = Bundle.main.object(forInfoDictionaryKey:key) else { + throw Error.missingKey + } + + switch object { + case let value as T: + return value + case let string as String: + guard let value = T(string) else { fallthrough } + return value + default: + throw Error.invalidValue + } + } +} + +var CONF_BASE_URL: String { + return try! Configuration.value(for: "BASE_URL") +} + +var CONF_GRPC_PORT: String { + return try! Configuration.value(for: "GRPC_PORT") +} diff --git a/Quickfeed/Config.xcconfig b/Quickfeed/Config.xcconfig new file mode 100644 index 0000000..9521ace --- /dev/null +++ b/Quickfeed/Config.xcconfig @@ -0,0 +1,13 @@ +// +// Config.xcconfig +// Quickfeed +// +// Created by Oskar Gjølga on 01/07/2021. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + + +BASE_URL = test.oskargjolga.com +GRPC_PORT = 9090 diff --git a/Quickfeed/Info.plist b/Quickfeed/Info.plist index 89b5f57..f3d7dbb 100644 --- a/Quickfeed/Info.plist +++ b/Quickfeed/Info.plist @@ -31,6 +31,10 @@ CFBundleVersion 1 + BASE_URL + $(BASE_URL) + GRPC_PORT + $(GRPC_PORT) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) diff --git a/Quickfeed/Managers/GRPCManager.swift b/Quickfeed/Managers/GRPCManager.swift index b7df332..fa3bddb 100644 --- a/Quickfeed/Managers/GRPCManager.swift +++ b/Quickfeed/Managers/GRPCManager.swift @@ -8,6 +8,8 @@ import NIO import GRPC import NIOHPACK + + class GRPCManager { static let shared = GRPCManager() @@ -18,9 +20,8 @@ class GRPCManager { var defaultOptions: CallOptions? private init(){ - let hostname = "localhost" - let port = 9090 - + let hostname = CONF_BASE_URL + let port = Int(CONF_GRPC_PORT)! let configuration = ClientConnection.Configuration( target: .hostAndPort(hostname, port), eventLoopGroup: eventLoopGroup, @@ -32,9 +33,12 @@ class GRPCManager { print("Connecting to \(hostname)") } - func createHeader(userID: UInt64){ + + + + func createHeader(sessionId: String){ self.defaultOptions = CallOptions() - self.defaultOptions!.customMetadata = ["custom-header-1": "value1", "user": "\(userID)"] + self.defaultOptions!.customMetadata = ["custom-header-1": "value1", "session": "\(sessionId)"] } func shutdown() { @@ -44,8 +48,8 @@ class GRPCManager { } // MARK: Users - func setUser(userID: UInt64){ - createHeader(userID: userID) + func setUserSession(sessionId: String){ + createHeader(sessionId: sessionId) } func getUser() -> User?{ diff --git a/Quickfeed/Managers/GitHubManager.swift b/Quickfeed/Managers/GitHubManager.swift index 55deb83..413ed5c 100644 --- a/Quickfeed/Managers/GitHubManager.swift +++ b/Quickfeed/Managers/GitHubManager.swift @@ -20,7 +20,7 @@ class GitHubManager: NSObject, ObservableObject, ASWebAuthenticationPresentation } func logInWithGitHub() { - self.viewModel.setUser(userID: 100) + //self.viewModel.setUser(userID: 100) /*guard let authURL = URL(string: "https://QuickFeed.no/auth/github/github") else { return } let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: "quickfeed", completionHandler: { (callbackURL, error) in guard error == nil, let callbackURL = callbackURL else { return } diff --git a/Quickfeed/Providers/ProviderProtocol.swift b/Quickfeed/Providers/ProviderProtocol.swift index 29c34cd..345e2c9 100644 --- a/Quickfeed/Providers/ProviderProtocol.swift +++ b/Quickfeed/Providers/ProviderProtocol.swift @@ -9,43 +9,65 @@ import NIO protocol ProviderProtocol{ - func setUser(userID: UInt64) + + + + // MARK: Users + func setUser(sessionId: String) func getUser() -> User? - func getCoursesForCurrentUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Course]? - func isAuthorizedTeacher() -> Bool - func getCourses() -> [Course]? func getUsers() -> [User]? func updateUser(user: User) - - func getCourse(courseId: UInt64) -> Course? - func getAssignments(courseID: UInt64) -> [Assignment] - func createEnrollment(courseID: UInt64, userID: UInt64) - func updateEnrollment(enrollment: Enrollment, status: Enrollment.UserStatus) + func isAuthorizedTeacher() -> Bool - func createNewCourse(course: Course) -> Course? - func updateCourse(course: Course) + // MARK: Groups + func getGroup(groupID: UInt64) -> Group? + func getGroupByUserAndCourse(courseID: UInt64, groupID: UInt64?, userID: UInt64) -> Group? + func getGroupsByCourse(courseID: UInt64) -> EventLoopFuture func createGroup(group: Group) -> EventLoopFuture + func updateGroup(group: Group) + func deleteGroup(userID: UInt64, groupID: UInt64, courseID: UInt64) + // MARK: Courses + func getCourse(courseID: UInt64) -> Course? + func getCourses() -> [Course]? + func getCoursesByUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Course]? + func createCourse(course: Course) -> Course? + func updateCourse(course: Course) + func updateCourseVisibility(enrollment: Enrollment) - func getGroupByUserAndCourse(courseId: UInt64, groupID: UInt64?, userId: UInt64) -> Group? - func getGroupsByCourse(courseId: UInt64) -> EventLoopFuture - func getSubmissionsByUser(courseId: UInt64, userId: UInt64) -> [Submission] - func getSubmissionsByGroub(courseId: UInt64, groupId: UInt64) -> [Submission] - func getSubmissionsByCourse(courseId: UInt64, type: SubmissionsForCourseRequest.TypeEnum) -> EventLoopFuture - func getEnrollmentsForUser(userId: UInt64) -> [Enrollment] - func getOrganization(orgName: String) -> EventLoopFuture - func getProviders() -> [String] - func updateAssignments(courseId: UInt64) -> Bool - func updateSubmission(courseId: UInt64, submisssion: Submission) -> Bool - func updateSubmissions(assignmentID: UInt64, courseID: UInt64, score: UInt32, release: Bool, approve: Bool) - func rebuildSubmission(assignmentId: UInt64, submissionId: UInt64) -> Submission? - func getRepositories(courseId: UInt64, types: [Repository.Type]) + // MARK: Assignments + func getAssignments(courseID: UInt64) -> [Assignment]? + func updateAssignments(courseID: UInt64) -> Bool - func getEnrollmentsByCourse(courseId: UInt64, ignoreGroupMembers: Bool?, withActivity: Bool?, userStatus: [Enrollment.UserStatus]) -> EventLoopFuture + // MARK: Enrollments + func getEnrollmentsByUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Enrollment]? + func getEnrollmentsByCourse(courseID: UInt64, ignoreGroupMembers: Bool?, withActivity: Bool?, userStatus: [Enrollment.UserStatus]) -> EventLoopFuture + func createEnrollment(enrollment: Enrollment) + func updateEnrollment(enrollment: Enrollment) + func updateEnrollments(courseID: UInt64) - func createReview(courseId: UInt64, review: Review) -> Review? - func getReviewers(submissionId: UInt64, courseId: UInt64) -> Reviewers? - func updateReview(courseId: UInt64, review: Review) - func loadCriteria(courseId: UInt64, assignmentId: UInt64) -> [GradingBenchmark] + // MARK: Submissions + func getSubmissions(userID: UInt64?, groupID: UInt64?, courseID: UInt64) -> [Submission]? + func getSubmissionsByCourse(courseID: UInt64, type: SubmissionsForCourseRequest.TypeEnum) -> EventLoopFuture + func updateSubmission(courseID: UInt64, submisssion: Submission) -> Bool + func updateSubmissions(assignmentID: UInt64, courseID: UInt64, score: UInt32, release: Bool, approve: Bool) + func rebuildSubmission(submissionID: UInt64, assignmentID: UInt64) -> Bool + + // MARK: Manual Grading + func createBenchmark(gradingBenchmark: GradingBenchmark) -> GradingBenchmark? + func updateBenchmark(gradingBenchmark: GradingBenchmark) + func deleteBenchmark(gradingBenchmark: GradingBenchmark) + func createCriterion(gradingCriterion: GradingCriterion) -> GradingCriterion? + func updateCriterion(gradingCriterion: GradingCriterion) + func deleteCriterion(gradingCriterion: GradingCriterion) + func createReview(courseID: UInt64, review: Review) -> Review? + func updateReview(courseID: UInt64, review: Review) + func getReviewers(submissionID: UInt64, courseID: UInt64) -> Reviewers? + func loadCriteria(courseID: UInt64, assignmentID: UInt64) -> [GradingBenchmark] + // MARK: Misc + func getProviders() -> [String]? + func getOrganization(orgName: String) -> EventLoopFuture + func getRepositories(courseID: UInt64, repositoryTypes: [Repository.TypeEnum]) -> Repositories? + func isEmptyRepo(userID: UInt64, groupID: UInt64, courseID: UInt64) } diff --git a/Quickfeed/Providers/ServerProvider.swift b/Quickfeed/Providers/ServerProvider.swift index 4a59b85..613d83f 100644 --- a/Quickfeed/Providers/ServerProvider.swift +++ b/Quickfeed/Providers/ServerProvider.swift @@ -7,166 +7,191 @@ import Foundation import NIO class ServerProvider: ProviderProtocol{ - - var grpcManager: GRPCManager = GRPCManager.shared static let shared: ServerProvider = ServerProvider() + var grpcManager: GRPCManager = GRPCManager.shared + private init(){ print("New ServerProvider") } - func setUser(userID: UInt64){ - grpcManager.setUser(userID: userID) + // MARK: Users + func setUser(sessionId: String){ + grpcManager.setUserSession(sessionId: sessionId) } func getUser() -> User? { return grpcManager.getUser() } + func getUsers() -> [User]? { + return grpcManager.getUsers() + } + func updateUser(user: User) { grpcManager.updateUser(user: user) } - func getCoursesForCurrentUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Course]? { - return grpcManager.getCoursesByUser(userID: userID, userStatus: userStatus) + func isAuthorizedTeacher() -> Bool { + return grpcManager.isAuthorizedTeacher() } - func getOrganization(orgName: String) -> EventLoopFuture { - return grpcManager.getOrganization(orgName: orgName) + // MARK: Groups + func getGroup(groupID: UInt64) -> Group? { + return grpcManager.getGroup(groupID: groupID) } - func isAuthorizedTeacher() -> Bool { - return grpcManager.isAuthorizedTeacher() + func getGroupByUserAndCourse(courseID: UInt64, groupID: UInt64?, userID: UInt64) -> Group? { + return self.grpcManager.getGroupByUserAndCourse(userID: userID, groupID: groupID, courseID: courseID) } - func getCourses() -> [Course]? { - return grpcManager.getCourses() + func getGroupsByCourse(courseID: UInt64) -> EventLoopFuture { + return self.grpcManager.getGroupsByCourse(courseID: courseID) } - func getUsers() -> [User]? { - return grpcManager.getUsers() + func createGroup(group: Group) -> EventLoopFuture { + return self.grpcManager.createGroup(group: group) } - - func getCourse(courseId: UInt64) -> Course? { - return self.grpcManager.getCourse(courseID: courseId) + + func updateGroup(group: Group) { + self.grpcManager.updateGroup(group: group) + } + + func deleteGroup(userID: UInt64, groupID: UInt64, courseID: UInt64) { + grpcManager.deleteGroup(userID: userID, groupID: groupID, courseID: courseID) } - func getAssignments(courseID: UInt64) -> [Assignment] { - let assignments = self.grpcManager.getAssignments(courseID: courseID) - if assignments != nil { - return assignments! - } - return [] + // MARK: Courses + func getCourse(courseID: UInt64) -> Course? { + return self.grpcManager.getCourse(courseID: courseID) } - func updateAssignments(courseId: UInt64) -> Bool { - return self.grpcManager.updateAssignments(courseID: courseId) + + func getCourses() -> [Course]? { + return grpcManager.getCourses() } - // ENROLLMENTS + func getCoursesByUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Course]? { + return grpcManager.getCoursesByUser(userID: userID, userStatus: userStatus) + } - func createEnrollment(courseID: UInt64, userID: UInt64) { - var enrollment = Enrollment() - enrollment.courseID = courseID - enrollment.userID = userID - - self.grpcManager.createEnrollment(enrollment: enrollment) + func createCourse(course: Course) -> Course? { + return grpcManager.createCourse(course: course) } - func getEnrollmentsForUser(userId: UInt64) -> [Enrollment] { - return self.grpcManager.getEnrollmentsByUser(userID: userId, userStatus: [Enrollment.UserStatus.teacher, Enrollment.UserStatus.student, Enrollment.UserStatus.pending])! + func updateCourse(course: Course) { + grpcManager.updateCourse(course: course) } - func updateEnrollment(enrollment: Enrollment, status: Enrollment.UserStatus) { - var enrollment = enrollment - enrollment.status = status - grpcManager.updateEnrollment(enrollment: enrollment) + func updateCourseVisibility(enrollment: Enrollment) { + grpcManager.updateCourseVisibility(enrollment: enrollment) } - - func getEnrollmentsByCourse(courseId: UInt64, ignoreGroupMembers: Bool?, withActivity: Bool?, userStatus: [Enrollment.UserStatus]) -> EventLoopFuture{ - return self.grpcManager.getEnrollmentsByCourse(courseID: courseId, ignoreGroupMembers: ignoreGroupMembers, withActivity: withActivity, userStatus: userStatus) + + // MARK: Assignments + func getAssignments(courseID: UInt64) -> [Assignment]? { + return grpcManager.getAssignments(courseID: courseID) } - // GROUPS - func createGroup(group: Group) -> EventLoopFuture { - return self.grpcManager.createGroup(group: group) + func updateAssignments(courseID: UInt64) -> Bool { + return self.grpcManager.updateAssignments(courseID: courseID) } - func updateGroup(group: Group) { - self.grpcManager.updateGroup(group: group) + // MARK: Enrollments + func getEnrollmentsByUser(userID: UInt64, userStatus: [Enrollment.UserStatus]) -> [Enrollment]? { + return grpcManager.getEnrollmentsByUser(userID: userID, userStatus: userStatus) } - func getGroupByUserAndCourse(courseId: UInt64, groupID: UInt64?, userId: UInt64) -> Group? { - return self.grpcManager.getGroupByUserAndCourse(userID: userId, groupID: groupID, courseID: courseId) + func getEnrollmentsByCourse(courseID: UInt64, ignoreGroupMembers: Bool?, withActivity: Bool?, userStatus: [Enrollment.UserStatus]) -> EventLoopFuture { + return self.grpcManager.getEnrollmentsByCourse(courseID: courseID, ignoreGroupMembers: ignoreGroupMembers, withActivity: withActivity, userStatus: userStatus) } - func getGroupsByCourse(courseId: UInt64) -> EventLoopFuture { - return self.grpcManager.getGroupsByCourse(courseID: courseId) + func createEnrollment(enrollment: Enrollment) { + grpcManager.createEnrollment(enrollment: enrollment) } + func updateEnrollment(enrollment: Enrollment) { + grpcManager.updateEnrollment(enrollment: enrollment) + } - // SUBMISSIONS - func getSubmissionsByUser(courseId: UInt64, userId: UInt64) -> [Submission] { - /*let submissions = self.grpcManager.getSubmissionsForEnrollment(courseId: courseId, userId: userId) - return submissions*/ - return self.grpcManager.getSubmissions(userID: userId, groupID: nil, courseID: courseId)! + func updateEnrollments(courseID: UInt64) { + grpcManager.updateEnrollments(courseID: courseID) } - func getSubmissionsByGroub(courseId: UInt64, groupId: UInt64) -> [Submission] { - return self.grpcManager.getSubmissions(userID: nil, groupID: groupId, courseID: courseId)! - //return self.grpcManager.getSubbmissionByGroup(courseID: courseId, groupID: groupId) + // MARK: Submissions + func getSubmissions(userID: UInt64?, groupID: UInt64?, courseID: UInt64) -> [Submission]? { + return grpcManager.getSubmissions(userID: userID, groupID: groupID, courseID: courseID) } - func getSubmissionsByCourse(courseId: UInt64, type: SubmissionsForCourseRequest.TypeEnum) -> EventLoopFuture { - return self.grpcManager.getSubmissionsByCourse(courseID: courseId, type: type) + func getSubmissionsByCourse(courseID: UInt64, type: SubmissionsForCourseRequest.TypeEnum) -> EventLoopFuture { + return self.grpcManager.getSubmissionsByCourse(courseID: courseID, type: type) } - func updateSubmission(courseId: UInt64, submisssion: Submission) -> Bool { - return self.grpcManager.updateSubmission(submissionID: submisssion.id, courseID: courseId, score: submisssion.score, released: submisssion.released, status: submisssion.status) + func updateSubmission(courseID: UInt64, submisssion: Submission) -> Bool { + return self.grpcManager.updateSubmission(submissionID: submisssion.id, courseID: courseID, score: submisssion.score, released: submisssion.released, status: submisssion.status) } func updateSubmissions(assignmentID: UInt64, courseID: UInt64, score: UInt32, release: Bool, approve: Bool) { grpcManager.updateSubmissions(courseID: courseID, assignmentID: assignmentID, scoreLimit: score, release: release, approve: approve) } + func rebuildSubmission(submissionID: UInt64, assignmentID: UInt64) -> Bool { + return grpcManager.rebuildSubmission(submissionID: submissionID, assignmentID: assignmentID) + } + + // MARK: Manual Grading + func createBenchmark(gradingBenchmark: GradingBenchmark) -> GradingBenchmark? { + return grpcManager.createBenchmark(gradingBenchmark: gradingBenchmark) + } - // MANUAL GRADING - func loadCriteria(courseId: UInt64, assignmentId: UInt64) -> [GradingBenchmark] { - return self.grpcManager.loadCriteria(courseID: courseId, assignmentID: assignmentId)! + func updateBenchmark(gradingBenchmark: GradingBenchmark) { + grpcManager.updateBenchmark(gradingBenchmark: gradingBenchmark) } - func createReview(courseId: UInt64, review: Review) -> Review?{ - return self.grpcManager.createReview(courseID: courseId, review: review) + func deleteBenchmark(gradingBenchmark: GradingBenchmark) { + grpcManager.deleteBenchmark(gradingBenchmark: gradingBenchmark) } - func updateReview(courseId: UInt64, review: Review){ - return self.grpcManager.updateReview(courseID: courseId, review: review) + func createCriterion(gradingCriterion: GradingCriterion) -> GradingCriterion? { + return grpcManager.createCriterion(gradingCriterion: gradingCriterion) } - func getReviewers(submissionId: UInt64, courseId: UInt64) -> Reviewers?{ - return self.grpcManager.getReviewers(submissionID: submissionId, courseID: courseId) + func updateCriterion(gradingCriterion: GradingCriterion) { + grpcManager.updateCriterion(gradingCriterion: gradingCriterion) } - // Courses - func createNewCourse(course: Course) -> Course? { - return grpcManager.createCourse(course: course) + func deleteCriterion(gradingCriterion: GradingCriterion) { + grpcManager.deleteCriterion(gradingCriterion: gradingCriterion) } - func updateCourse(course: Course) { - grpcManager.updateCourse(course: course) + func createReview(courseID: UInt64, review: Review) -> Review?{ + return self.grpcManager.createReview(courseID: courseID, review: review) } - // NOT IMPLEMENTED - - func getProviders() -> [String] { - fatalError("Not implemented") + func updateReview(courseID: UInt64, review: Review){ + return self.grpcManager.updateReview(courseID: courseID, review: review) + } + + func getReviewers(submissionID: UInt64, courseID: UInt64) -> Reviewers?{ + return self.grpcManager.getReviewers(submissionID: submissionID, courseID: courseID) } - func rebuildSubmission(assignmentId: UInt64, submissionId: UInt64) -> Submission? { - fatalError("Not implemented") + func loadCriteria(courseID: UInt64, assignmentID: UInt64) -> [GradingBenchmark] { + return self.grpcManager.loadCriteria(courseID: courseID, assignmentID: assignmentID)! } - func getRepositories(courseId: UInt64, types: [Repository.Type]) { - fatalError("Not implemented") + // MARK: Misc + func getProviders() -> [String]? { + return grpcManager.getProviders() } + func getOrganization(orgName: String) -> EventLoopFuture { + return grpcManager.getOrganization(orgName: orgName) + } + + func getRepositories(courseID: UInt64, repositoryTypes: [Repository.TypeEnum]) -> Repositories? { + return grpcManager.getRepositories(courseID: courseID, repositoryTypes: repositoryTypes) + } + + func isEmptyRepo(userID: UInt64, groupID: UInt64, courseID: UInt64) { + grpcManager.isEmptyRepo(userID: userID, groupID: groupID, courseID: courseID) + } } diff --git a/Quickfeed/ViewModels/StudentViewModel.swift b/Quickfeed/ViewModels/StudentViewModel.swift index e5ad732..a721094 100644 --- a/Quickfeed/ViewModels/StudentViewModel.swift +++ b/Quickfeed/ViewModels/StudentViewModel.swift @@ -22,7 +22,7 @@ class StudentViewModel: UserViewModelProtocol{ func setCourse(course: Course){ self.course = course - self.group = provider.getGroupByUserAndCourse(courseId: course.id, groupID: nil, userId: user.id) + self.group = provider.getGroupByUserAndCourse(courseID: course.id, groupID: nil, userID: user.id) } func createGroup(name: String, enrollments: [Enrollment]) { @@ -54,7 +54,7 @@ class StudentViewModel: UserViewModelProtocol{ } func getEnrollmentForCurrentCourse() -> Enrollment?{ - let enrollments = provider.getEnrollmentsForUser(userId: user.id) + let enrollments = provider.getEnrollmentsByUser(userID: self.user.id, userStatus: [Enrollment.UserStatus.teacher, Enrollment.UserStatus.student, Enrollment.UserStatus.pending])! for element in enrollments{ if element.course.id == course!.id{ return element @@ -64,7 +64,7 @@ class StudentViewModel: UserViewModelProtocol{ } func getEnrollmentsByCourse() { - let response = self.provider.getEnrollmentsByCourse(courseId: self.course!.id, ignoreGroupMembers: true, withActivity: nil, userStatus: [Enrollment.UserStatus.student]) + let response = self.provider.getEnrollmentsByCourse(courseID: self.course!.id, ignoreGroupMembers: true, withActivity: nil, userStatus: [Enrollment.UserStatus.student]) _ = response.always {(response: Result) in switch response { case .success(let response): @@ -79,7 +79,7 @@ class StudentViewModel: UserViewModelProtocol{ } func getAssignments(){ - self.assignments = provider.getAssignments(courseID: course!.id) + self.assignments = provider.getAssignments(courseID: course!.id)! } func hasGroupAssignments() -> Bool{ @@ -109,15 +109,15 @@ class StudentViewModel: UserViewModelProtocol{ } func getSubmissions(){ - var submissions = provider.getSubmissionsByUser(courseId: course!.id, userId: user.id) + var submissions = provider.getSubmissions(userID: user.id, groupID: nil, courseID: course!.id)! if self.group != nil{ - submissions.append(contentsOf: provider.getSubmissionsByGroub(courseId: course!.id, groupId: group!.id)) + submissions.append(contentsOf: provider.getSubmissions(userID: nil, groupID: group!.id, courseID: course!.id)!) } self.submissions = submissions } func getSlipdays() -> UInt32? { - let enrollments = provider.getEnrollmentsForUser(userId: user.id) + let enrollments = provider.getEnrollmentsByUser(userID: self.user.id, userStatus: [Enrollment.UserStatus.teacher, Enrollment.UserStatus.student, Enrollment.UserStatus.pending])! for element in enrollments{ if element.courseID == course!.id{ return element.slipDaysRemaining diff --git a/Quickfeed/ViewModels/TeacherViewModel.swift b/Quickfeed/ViewModels/TeacherViewModel.swift index 1d14787..5543115 100644 --- a/Quickfeed/ViewModels/TeacherViewModel.swift +++ b/Quickfeed/ViewModels/TeacherViewModel.swift @@ -37,7 +37,7 @@ class TeacherViewModel: UserViewModelProtocol{ } func loadGroups(){ - let response = self.provider.getGroupsByCourse(courseId: self.currentCourse.id) + let response = self.provider.getGroupsByCourse(courseID: self.currentCourse.id) _ = response.always {(response: Result) in switch response { case .success(let response): @@ -69,7 +69,7 @@ class TeacherViewModel: UserViewModelProtocol{ } func loadEnrollmentLinks(){ - let response = self.provider.getSubmissionsByCourse(courseId: self.currentCourse.id, type: SubmissionsForCourseRequest.TypeEnum.all) + let response = self.provider.getSubmissionsByCourse(courseID: self.currentCourse.id, type: SubmissionsForCourseRequest.TypeEnum.all) _ = response.always {(response: Result) in switch response { case .success(let response): @@ -84,7 +84,7 @@ class TeacherViewModel: UserViewModelProtocol{ } func loadEnrollments(){ - let response = self.provider.getEnrollmentsByCourse(courseId: self.currentCourse.id, ignoreGroupMembers: nil, withActivity: nil, userStatus: [Enrollment.UserStatus.student, Enrollment.UserStatus.teacher, Enrollment.UserStatus.pending]) + let response = self.provider.getEnrollmentsByCourse(courseID: self.currentCourse.id, ignoreGroupMembers: nil, withActivity: nil, userStatus: [Enrollment.UserStatus.student, Enrollment.UserStatus.teacher, Enrollment.UserStatus.pending]) _ = response.always {(response: Result) in switch response { case .success(let response): @@ -99,16 +99,16 @@ class TeacherViewModel: UserViewModelProtocol{ } func loadAssignments(){ - self.assignments = self.provider.getAssignments(courseID: self.currentCourse.id) + self.assignments = self.provider.getAssignments(courseID: self.currentCourse.id)! self.loadManuallyGradedAssignments(courseId: self.currentCourse.id) } func updateAssignments() -> Bool{ - return self.provider.updateAssignments(courseId: self.currentCourse.id) + return self.provider.updateAssignments(courseID: self.currentCourse.id) } func updateSubmission(submission: Submission) -> Bool{ - return provider.updateSubmission(courseId: self.currentCourse.id, submisssion: submission) + return provider.updateSubmission(courseID: self.currentCourse.id, submisssion: submission) } func loadManuallyGradedAssignments(courseId: UInt64){ @@ -118,7 +118,7 @@ class TeacherViewModel: UserViewModelProtocol{ } func getSubmissionsByUser(courseId: UInt64, userId: UInt64) -> [Submission]{ - return self.provider.getSubmissionsByUser(courseId: courseId, userId: userId) + return self.provider.getSubmissions(userID: userId, groupID: nil, courseID: courseId)! } func getUserName(userId: UInt64) -> String{ @@ -134,26 +134,29 @@ class TeacherViewModel: UserViewModelProtocol{ } func updateEnrollment(enrollment: Enrollment, status: Enrollment.UserStatus){ - self.provider.updateEnrollment(enrollment: enrollment, status: status) + var enrollment = enrollment + enrollment.status = status + + self.provider.updateEnrollment(enrollment: enrollment) } // MANUAL GRADING func createReview(review: Review) -> Review?{ - return self.provider.createReview(courseId: self.currentCourse.id, review: review) + return self.provider.createReview(courseID: self.currentCourse.id, review: review) } func updateReview(review: Review){ - self.provider.updateReview(courseId: self.currentCourse.id, review: review) + self.provider.updateReview(courseID: self.currentCourse.id, review: review) } func getSubmissionByAssignment(userId: UInt64, assigmentID: UInt64) -> Submission{ - let submissions = provider.getSubmissionsByUser(courseId: self.currentCourse.id, userId: userId) + let submissions = provider.getSubmissions(userID: userId, groupID: nil, courseID: self.currentCourse.id)! return submissions.first(where: {$0.assignmentID == assigmentID})! } func loadCriteria(assignmentId: UInt64) -> [GradingBenchmark]{ - return self.provider.loadCriteria(courseId: currentCourse.id, assignmentId: assignmentId) + return self.provider.loadCriteria(courseID: currentCourse.id, assignmentID: assignmentId) } func reset() { diff --git a/Quickfeed/ViewModels/UserViewModel.swift b/Quickfeed/ViewModels/UserViewModel.swift index b4b0911..22acd65 100644 --- a/Quickfeed/ViewModels/UserViewModel.swift +++ b/Quickfeed/ViewModels/UserViewModel.swift @@ -17,12 +17,12 @@ class UserViewModel: UserViewModelProtocol { // User - func setUser(userID: UInt64){ - self.provider.setUser(userID: userID) - self.getUser() + func setUser(sessionId: String){ + self.provider.setUser(sessionId: sessionId) } func getUser() { + print("get user") self.user = provider.getUser()! self.getAllCoursesForCurrentUser() self.getEnrollments() @@ -50,7 +50,7 @@ class UserViewModel: UserViewModelProtocol { } func getCourseById(courseId: UInt64) -> Course{ - return self.provider.getCourse(courseId: courseId)! + return self.provider.getCourse(courseID: courseId)! } func getAllCourses() -> [Course]? { @@ -58,7 +58,7 @@ class UserViewModel: UserViewModelProtocol { } func getAllCoursesForCurrentUser() { - self.courses = self.provider.getCoursesForCurrentUser(userID: self.user!.id, userStatus: [Enrollment.UserStatus.student, Enrollment.UserStatus.teacher]) + self.courses = self.provider.getCoursesByUser(userID: self.user!.id, userStatus: [Enrollment.UserStatus.student, Enrollment.UserStatus.teacher]) } func isTeacherForCourse(courseId: UInt64) -> Bool? { @@ -82,7 +82,11 @@ class UserViewModel: UserViewModelProtocol { // Enrollment func createEnrollment(courseID: UInt64) { - self.provider.createEnrollment(courseID: courseID, userID: self.user!.id) + var enrollment = Enrollment() + enrollment.courseID = courseID + enrollment.userID = self.user!.id + + self.provider.createEnrollment(enrollment: enrollment) self.getEnrollments() } @@ -98,7 +102,7 @@ class UserViewModel: UserViewModelProtocol { } func getEnrollments() { - self.enrollments = self.provider.getEnrollmentsForUser(userId: self.user!.id) + self.enrollments = self.provider.getEnrollmentsByUser(userID: self.user!.id, userStatus: [Enrollment.UserStatus.teacher, Enrollment.UserStatus.student, Enrollment.UserStatus.pending])! if self.enrollments.count != 0 { self.sortEnrollmentsByCode() } diff --git a/Quickfeed/Views/AuthWebView.swift b/Quickfeed/Views/AuthWebView.swift new file mode 100644 index 0000000..d8597ea --- /dev/null +++ b/Quickfeed/Views/AuthWebView.swift @@ -0,0 +1,132 @@ +// +// AuthWebView.swift +// Quickfeed +// +// WebView in SwiftUI: https://stackoverflow.com/questions/62962063/implement-webkit-with-swiftui-on-macos-and-create-a-preview-of-a-webpage +// + +import SwiftUI +import WebKit +import Combine +import Foundation +import AppKit + +struct AuthWebView: View { + @ObservedObject var viewModel: UserViewModel + @ObservedObject var webViewModel: WebViewModel + @Binding var signingIn: Bool + @Binding var signedIn: Bool + + init(viewModel: UserViewModel, mesgURL: String, signingIn: Binding, signedIn: Binding) { + self.viewModel = viewModel + self.webViewModel = WebViewModel(link: mesgURL) + self._signingIn = signingIn + self._signedIn = signedIn + } + + var body: some View { + VStack{ + SwiftUIWebView(viewModel: webViewModel) + .onChange(of: webViewModel.link, perform: { value in + do{ + sleep(3) + } + if let sessionString = webViewModel.siteData["session"] as? String{ + print(sessionString) + viewModel.setUser(sessionId: sessionString) + signingIn = false + signedIn = true + + } else { + print("Failed to retrieve session") + } + }) + } + } +} + + +class WebViewModel: ObservableObject { + @Published var siteData: [String : Any] + @Published var link: String + @Published var didFinishLoading: Bool = false + @Published var pageTitle: String + @Published var hasSession: Bool = false + + init (link: String) { + self.link = link + self.pageTitle = "" + self.siteData = [:] + } +} + +struct SwiftUIWebView: NSViewRepresentable { + + public typealias NSViewType = WKWebView + @ObservedObject var viewModel: WebViewModel + + private let webView: WKWebView = WKWebView() + public func makeNSView(context: NSViewRepresentableContext) -> WKWebView { + webView.navigationDelegate = context.coordinator + webView.uiDelegate = context.coordinator as? WKUIDelegate + webView.load(Foundation.URLRequest(url: URL(string: viewModel.link)!)) + return webView + } + + public func updateNSView(_ nsView: WKWebView, context: NSViewRepresentableContext) { } + + public func makeCoordinator() -> Coordinator { + return Coordinator(viewModel) + } + + class Coordinator: NSObject, WKNavigationDelegate { + private var viewModel: WebViewModel + + init(_ viewModel: WebViewModel) { + self.viewModel = viewModel + } + + public func webView(_: WKWebView, didFail: WKNavigation!, withError: Error) { } + + public func webView(_: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) { } + + public func webView(_ web: WKWebView, didFinish: WKNavigation!) { + self.viewModel.pageTitle = web.title! + self.viewModel.link = web.url!.absoluteString + self.viewModel.didFinishLoading = true + web.getCookies(for: CONF_BASE_URL){data in + self.viewModel.siteData = data + } + } + + public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { } + + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) { + decisionHandler(.allow) + } + + } + +} + + + + +extension WKWebView { + private var httpCookieStore: WKHTTPCookieStore { return WKWebsiteDataStore.default().httpCookieStore } + func getCookies(for domain: String? = nil, completion: @escaping ([String : String])->()) { + var cookieDict = [String : String]() + httpCookieStore.getAllCookies { cookies in + for cookie in cookies { + if let domain = domain { + if cookie.domain.contains(domain) { + cookieDict[cookie.name] = cookie.value as String? + } + } else { + cookieDict[cookie.name] = cookie.value as String? + } + } + completion(cookieDict) + } + } +} diff --git a/Quickfeed/Views/Helpers/GitHubLogIn.swift b/Quickfeed/Views/Helpers/GitHubLogIn.swift index d171a67..1071939 100644 --- a/Quickfeed/Views/Helpers/GitHubLogIn.swift +++ b/Quickfeed/Views/Helpers/GitHubLogIn.swift @@ -5,7 +5,7 @@ import SwiftUI -struct GitHubLogIn: View { +struct GitHubLogInButton: View { @ObservedObject var viewModel: UserViewModel @Binding var login: Bool @@ -23,11 +23,5 @@ struct GitHubLogIn: View { .foregroundColor(.white) .cornerRadius(10.0) .contentShape(Rectangle()) - .onTapGesture { - GitHubManager(viewModel: viewModel).logInWithGitHub() - if viewModel.user != nil{ - login = !login - } - } } } diff --git a/Quickfeed/Views/LogIn.swift b/Quickfeed/Views/LogIn.swift index c3a0b97..7b4fc33 100644 --- a/Quickfeed/Views/LogIn.swift +++ b/Quickfeed/Views/LogIn.swift @@ -8,6 +8,10 @@ import SwiftUI struct LogIn: View { @ObservedObject var viewModel: UserViewModel @Binding var login: Bool + @State var signingIn: Bool = false + @State var signedIn: Bool = false + @State var authUrl: String = "https://\(CONF_BASE_URL)/app/login/login/github" + var body: some View { VStack{ @@ -25,8 +29,25 @@ struct LogIn: View { } } .padding(.horizontal) - GitHubLogIn(viewModel: viewModel, login: $login) + TextField("Hostname", text: $authUrl) + GitHubLogInButton(viewModel: viewModel, login: $login) + .onTapGesture { + signingIn = true + } + .sheet(isPresented: $signingIn, content: { + AuthWebView(viewModel: viewModel, + mesgURL: authUrl, + signingIn: $signingIn, + signedIn: $signedIn + ) + .frame(width: 600, height: 600) + }) } - .frame(width: 300, height: 165) + .onChange(of: signedIn, perform: { data in + viewModel.getUser() + }) + + .frame(width: 300, height: 300) + } } diff --git a/qf.db b/qf.db new file mode 100644 index 0000000..98d6c0b Binary files /dev/null and b/qf.db differ