working model for network layer, added user object

This commit is contained in:
aj 2020-02-18 21:43:30 +00:00
parent d91ce89670
commit b80152ba13
12 changed files with 351 additions and 85 deletions

View File

@ -7,6 +7,12 @@
objects = {
/* Begin PBXBuildFile section */
E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; };
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45823FC50EC00635494 /* SwiftyJSON */; };
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45A23FC748D00635494 /* UserApi.swift */; };
E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45D23FC83AF00635494 /* KeychainAccess */; };
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45F23FC85D600635494 /* PlaylistApi.swift */; };
E97AF46223FC89CC00635494 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E97AF46123FC89CB00635494 /* Main.storyboard */; };
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254BC23F9B7A90056D9D3 /* Playlist.swift */; };
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C123F9FFF90056D9D3 /* PlaylistView.swift */; };
E98254C823FA25D20056D9D3 /* PlaylistList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254C723FA25D20056D9D3 /* PlaylistList.swift */; };
@ -42,6 +48,10 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
E97AF45F23FC85D600635494 /* PlaylistApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistApi.swift; sourceTree = "<group>"; };
E97AF46123FC89CB00635494 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
E98254BC23F9B7A90056D9D3 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistView.swift; sourceTree = "<group>"; };
E98254C723FA25D20056D9D3 /* PlaylistList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistList.swift; sourceTree = "<group>"; };
@ -69,7 +79,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */,
E98254D923FB53780056D9D3 /* Alamofire in Frameworks */,
E97AF45E23FC83AF00635494 /* KeychainAccess in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -94,6 +106,7 @@
isa = PBXGroup;
children = (
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
E97AF45523FC4E7800635494 /* User.swift */,
);
path = Model;
sourceTree = "<group>";
@ -102,6 +115,8 @@
isa = PBXGroup;
children = (
E98254DA23FB64740056D9D3 /* Network.swift */,
E97AF45A23FC748D00635494 /* UserApi.swift */,
E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
);
path = Network;
sourceTree = "<group>";
@ -124,6 +139,7 @@
E9EA691523F9A54B0012C3E8 /* LaunchScreen.storyboard */,
E9EA690A23F9A5430012C3E8 /* AppDelegate.swift */,
E9EA690C23F9A5430012C3E8 /* SceneDelegate.swift */,
E97AF46123FC89CB00635494 /* Main.storyboard */,
);
path = Application;
sourceTree = "<group>";
@ -206,6 +222,8 @@
name = "Music Tools";
packageProductDependencies = (
E98254D823FB53780056D9D3 /* Alamofire */,
E97AF45823FC50EC00635494 /* SwiftyJSON */,
E97AF45D23FC83AF00635494 /* KeychainAccess */,
);
productName = "Music Tools";
productReference = E9EA690723F9A5430012C3E8 /* Music Tools.app */;
@ -281,6 +299,8 @@
mainGroup = E9EA68FE23F9A5430012C3E8;
packageReferences = (
E98254D723FB53770056D9D3 /* XCRemoteSwiftPackageReference "alamofire" */,
E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */,
E97AF45C23FC83AF00635494 /* XCRemoteSwiftPackageReference "keychainaccess" */,
);
productRefGroup = E9EA690823F9A5430012C3E8 /* Products */;
projectDirPath = "";
@ -301,6 +321,7 @@
E9EA691723F9A54B0012C3E8 /* LaunchScreen.storyboard in Resources */,
E9EA691423F9A54B0012C3E8 /* Preview Assets.xcassets in Resources */,
E9EA691123F9A54A0012C3E8 /* Assets.xcassets in Resources */,
E97AF46223FC89CC00635494 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -329,11 +350,14 @@
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
E98254C823FA25D20056D9D3 /* PlaylistList.swift in Sources */,
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */,
E98254C223F9FFF90056D9D3 /* PlaylistView.swift in Sources */,
E97AF45623FC4E7800635494 /* User.swift in Sources */,
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -660,6 +684,22 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/swiftyjson/swiftyjson";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.0;
};
};
E97AF45C23FC83AF00635494 /* XCRemoteSwiftPackageReference "keychainaccess" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kishikawakatsumi/keychainaccess";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.1.0;
};
};
E98254D723FB53770056D9D3 /* XCRemoteSwiftPackageReference "alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/alamofire/alamofire.git";
@ -671,6 +711,16 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
E97AF45823FC50EC00635494 /* SwiftyJSON */ = {
isa = XCSwiftPackageProductDependency;
package = E97AF45723FC50EC00635494 /* XCRemoteSwiftPackageReference "swiftyjson" */;
productName = SwiftyJSON;
};
E97AF45D23FC83AF00635494 /* KeychainAccess */ = {
isa = XCSwiftPackageProductDependency;
package = E97AF45C23FC83AF00635494 /* XCRemoteSwiftPackageReference "keychainaccess" */;
productName = KeychainAccess;
};
E98254D823FB53780056D9D3 /* Alamofire */ = {
isa = XCSwiftPackageProductDependency;
package = E98254D723FB53770056D9D3 /* XCRemoteSwiftPackageReference "alamofire" */;

View File

@ -9,6 +9,24 @@
"revision": "0c8cb78d05b6d067ee331c05058ff4dedcb45ffa",
"version": "5.0.0"
}
},
{
"package": "KeychainAccess",
"repositoryURL": "https://github.com/kishikawakatsumi/keychainaccess",
"state": {
"branch": null,
"revision": "b920ad7df3c73189dcdd4aa05c540849b2010dbf",
"version": "4.1.0"
}
},
{
"package": "SwiftyJSON",
"repositoryURL": "https://github.com/swiftyjson/swiftyjson",
"state": {
"branch": null,
"revision": "2b6054efa051565954e1d2b9da831680026cd768",
"version": "5.0.0"
}
}
]
},

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
</dependencies>
<scenes/>
</document>

View File

@ -8,6 +8,7 @@
import UIKit
import SwiftUI
import KeychainAccess
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@ -22,6 +23,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Create the SwiftUI view that provides the window contents.
let contentView = RootView()
let keychain = Keychain(service: "xyz.sarsoo.music.login")
keychain["username"] = ""
keychain["password"] = ""
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)

View File

@ -7,7 +7,7 @@
//
import UIKit
import SwiftyJSON
class Playlist: Identifiable {
@ -55,5 +55,19 @@ class Playlist: Identifiable {
self.shuffle = shuffle
}
static func fromDict(dictionary: JSON) -> Playlist {
return Playlist(name: dictionary["name"].stringValue,
uri: dictionary["uri"].stringValue,
username: dictionary["username"].stringValue,
include_recommendations: dictionary["include_recommendations"].boolValue,
recommendation_sample: dictionary["recommendation_sample"].intValue,
include_library_tracks: dictionary["include_library_tracks"].boolValue,
parts: dictionary["parts"].arrayObject as! Array<String>,
playlist_references: dictionary["playlist_references"].arrayObject as! Array<String>,
shuffle: dictionary["shuffle"].boolValue)
}
}

View File

@ -0,0 +1,58 @@
//
// User.swift
// Music Tools
//
// Created by Andy Pack on 18/02/2020.
// Copyright © 2020 Sarsoo. All rights reserved.
//
import UIKit
import SwiftyJSON
enum UserType: String {
case user = "user"
case admin = "admin"
}
class User: Identifiable {
//MARK: Properties
var username: String
var email: String?
var type: UserType
var last_login: String
var spotify_linked: Bool
var lastfm_username: String?
//MARK: Initialization
init(username: String,
email: String?,
type: UserType = .user,
last_login: String,
spotify_linked: Bool,
lastfm_username: String?){
self.username = username
self.email = email
self.type = type
self.last_login = last_login
self.spotify_linked = spotify_linked
self.lastfm_username = lastfm_username
}
static func fromDict(dictionary: JSON) -> User {
return User(username: dictionary["username"].stringValue,
email: dictionary["username"].stringValue,
type: UserType(rawValue: dictionary["type"].stringValue) ?? .user,
last_login: dictionary["last_login"].stringValue,
spotify_linked: dictionary["spotify_linked"].boolValue,
lastfm_username: dictionary["lastfm_username"].stringValue)
}
}

View File

@ -8,73 +8,63 @@
import Foundation
import Alamofire
import SwiftyJSON
import KeychainAccess
class MusicToolsNetwork {
public enum AuthMethod {
case basic
var baseBath: String = "https://music.sarsoo.xyz/"
func auth(headers: Alamofire.HTTPHeaders?) -> Alamofire.HTTPHeaders {
switch self {
case .basic:
var txHeaders = headers ?? HTTPHeaders()
public func request(path: String,
method: Alamofire.HTTPMethod,
parameters: [String:String]? ,
encoder: Alamofire.ParameterEncoder?,
headers: Alamofire.HTTPHeaders? ) {
guard let uwParameters = parameters else {
AF.request(baseBath + path,
method: method,
headers: headers ).validate().response { response in
debugPrint(response)
let keychain = Keychain(service: "xyz.sarsoo.music.login")
txHeaders.add(.authorization(username: keychain["username"] ?? "", password: keychain["password"] ?? ""))
return txHeaders
}
return
}
AF.request(baseBath + path,
method: method,
parameters: uwParameters,
headers: headers ).response { response in
debugPrint(response)
}
}
}
class BasicAuthNetwork: MusicToolsNetwork {
var username: String
var password: String
struct RequestBuilder {
static func buildRequest(apiRequest: ApiRequest) -> Alamofire.DataRequest {
init(username: String, password: String) {
self.username = username
self.password = password
let txHeaders = apiRequest.authMethod?.auth(headers: apiRequest.headers)
if apiRequest.parameters != nil {
if apiRequest.parameterType != nil {
let txEncoder = apiRequest.parameterType ?? JSONParameterEncoder.default
return AF.request(apiRequest.domain + apiRequest.path,
method: apiRequest.httpMethod,
parameters: apiRequest.parameters,
encoder: txEncoder,
headers: txHeaders)
} else {
return AF.request(apiRequest.domain + apiRequest.path,
method: apiRequest.httpMethod,
parameters: apiRequest.parameters,
headers: txHeaders)
}
func getHeader() -> String {
return "\(username):\(password)".toBase64()
}
public func authedRequest(path: String,
method: Alamofire.HTTPMethod,
parameters: [String:String]?,
encoder: Alamofire.ParameterEncoder?,
headers: Alamofire.HTTPHeaders? ) {
let encoded = "\(username):\(password)".toBase64()
var txHeaders = headers
if headers == nil {
txHeaders = Alamofire.HTTPHeaders()
}
txHeaders?.add(name: "Authorization", value: "Basic \(encoded)")
request(path: path, method: method, parameters: parameters, encoder: encoder, headers: txHeaders)
return AF.request(apiRequest.domain + apiRequest.path,
method: apiRequest.httpMethod,
headers: txHeaders)
}
}
extension String {
func toBase64() -> String {
return Data(self.utf8).base64EncodedString()
}
struct ApiRequestDefaults {
static let authMethod: AuthMethod = .basic
static let domain: String = "https://music.sarsoo.xyz/"
}
protocol ApiRequest {
var domain: String { get }
var path: String { get }
var httpMethod: Alamofire.HTTPMethod { get }
var parameters: JSON? { get }
var parameterType: Alamofire.ParameterEncoder? { get }
var headers: HTTPHeaders? { get }
var authMethod: AuthMethod? { get }
}

View File

@ -0,0 +1,54 @@
//
// PlaylistApi.swift
// Music Tools
//
// Created by Andy Pack on 18/02/2020.
// Copyright © 2020 Sarsoo. All rights reserved.
//
import Foundation
import Alamofire
import SwiftyJSON
public enum PlaylistApi {
case getPlaylists
}
extension PlaylistApi: ApiRequest {
var domain: String {
return ApiRequestDefaults.domain
}
var path: String {
switch self {
case .getPlaylists:
return "api/playlists"
}
}
var httpMethod: Alamofire.HTTPMethod {
switch self {
case .getPlaylists:
return .get
}
}
var parameters: JSON? {
return nil
}
var parameterType: ParameterEncoder? {
return nil
}
var headers: HTTPHeaders? {
return nil
}
var authMethod: AuthMethod? {
return ApiRequestDefaults.authMethod
}
}

View File

@ -0,0 +1,53 @@
//
// UserApi.swift
// Music Tools
//
// Created by Andy Pack on 18/02/2020.
// Copyright © 2020 Sarsoo. All rights reserved.
//
import Foundation
import Alamofire
import SwiftyJSON
public enum UserApi {
case getUser
}
extension UserApi: ApiRequest {
var domain: String {
return ApiRequestDefaults.domain
}
var path: String {
switch self {
case .getUser:
return "api/user"
}
}
var httpMethod: Alamofire.HTTPMethod {
switch self {
case .getUser:
return .get
}
}
var parameters: JSON? {
return nil
}
var parameterType: ParameterEncoder? {
return nil
}
var headers: HTTPHeaders? {
return nil
}
var authMethod: AuthMethod? {
return ApiRequestDefaults.authMethod
}
}

View File

@ -12,11 +12,13 @@ struct PlaylistRow: View {
var playlist: Playlist
var body: some View {
NavigationLink(destination: PlaylistView(playlist: playlist)){
HStack {
Text(playlist.name)
Spacer()
}
}
}
}
struct PlaylistRow_Previews: PreviewProvider {

View File

@ -10,6 +10,8 @@ import SwiftUI
struct PlaylistView: View {
var playlist: Playlist
@State private var recommendations: Bool = false
@State private var library_Tracks: Bool = false
var body: some View {
@ -25,10 +27,11 @@ struct PlaylistView: View {
.cornerRadius(18)
.padding(.bottom, 20)
Toggle(isOn: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Value@*/.constant(true)/*@END_MENU_TOKEN@*/) {
Toggle(isOn: $recommendations) {
Text("Spotify Recommendations")
}
if $recommendations.wrappedValue {
Stepper(value: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Value@*/.constant(4)/*@END_MENU_TOKEN@*/, in: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Range@*/1...10/*@END_MENU_TOKEN@*/){
Text("#:")
.foregroundColor(Color.gray)
@ -38,15 +41,18 @@ struct PlaylistView: View {
.multilineTextAlignment(.trailing)
}
Toggle(isOn: /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Value@*/.constant(true)/*@END_MENU_TOKEN@*/) {
Text("Library Tracks")
}
EditButton()
Toggle(isOn: $library_Tracks) {
Text("Library Tracks")
}
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.onAppear {
self.$recommendations.wrappedValue = self.playlist.include_recommendations
self.$library_Tracks.wrappedValue = self.playlist.include_library_tracks
}
}
}

View File

@ -8,17 +8,17 @@
import SwiftUI
import Alamofire
import SwiftyJSON
struct RootView: View {
@State private var selection = 0
@State private var playlists: Array<Playlist> = []
var body: some View {
TabView(selection: $selection){
NavigationView {
List(/*@START_MENU_TOKEN@*/0 ..< 5/*@END_MENU_TOKEN@*/) { item in
Text("Playlist")
.font(.title)
List(playlists) { playlist in
PlaylistRow(playlist: playlist)
}
.navigationBarTitle(Text("Playlists").font(.title))
}
@ -63,12 +63,21 @@ struct RootView: View {
}
private func fetch() {
let net: BasicAuthNetwork = BasicAuthNetwork(username: "", password: "")
net.authedRequest(path: "api/playlist",
method: Alamofire.HTTPMethod.get,
parameters: ["name": ""],
encoder: nil,
headers: nil)
let api = PlaylistApi.getPlaylists
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
guard let data = response.data else {
fatalError("error getting playlists")
}
guard let json = try? JSON(data: data) else {
fatalError("error parsing reponse")
}
self.playlists = json["playlists"].arrayValue.map({ dict in
Playlist.fromDict(dictionary: dict)
}).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
}
}
}