Resume last login
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
341F5F0F2A15223A00962D48 /* SeibroRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F0E2A15223A00962D48 /* SeibroRequest.swift */; };
|
||||
341F5F112A1685E700962D48 /* ShopRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F102A1685E700962D48 /* ShopRequest.swift */; };
|
||||
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */; };
|
||||
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -73,6 +74,7 @@
|
||||
341F5F0E2A15223A00962D48 /* SeibroRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeibroRequest.swift; sourceTree = "<group>"; };
|
||||
341F5F102A1685E700962D48 /* ShopRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopRequest.swift; sourceTree = "<group>"; };
|
||||
341F5F132A16CD7A00962D48 /* DomesticShopProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticShopProduct.swift; sourceTree = "<group>"; };
|
||||
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissProfile.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -121,6 +123,7 @@
|
||||
341F5EDF2A0F372000962D48 /* Login */,
|
||||
341F5EAE2A0A80EC00962D48 /* KissMe.h */,
|
||||
341F5EAF2A0A80EC00962D48 /* KissMe.docc */,
|
||||
349C26AA2A1EAE2400F3EC91 /* KissProfile.swift */,
|
||||
341F5EF42A0F891200962D48 /* KissAccount.swift */,
|
||||
341F5F0A2A15115400962D48 /* KissShop.swift */,
|
||||
);
|
||||
@@ -315,6 +318,7 @@
|
||||
341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */,
|
||||
341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */,
|
||||
341F5EDE2A0F300100962D48 /* Request.swift in Sources */,
|
||||
349C26AB2A1EAE2400F3EC91 /* KissProfile.swift in Sources */,
|
||||
341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */,
|
||||
341F5F142A16CD7A00962D48 /* DomesticShopProduct.swift in Sources */,
|
||||
341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */,
|
||||
|
||||
@@ -8,65 +8,12 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
public class KissAccount {
|
||||
public class KissAccount: KissProfile {
|
||||
|
||||
let credential: Credential
|
||||
var profileLock = NSLock()
|
||||
var profile = Profile()
|
||||
|
||||
var accessToken: String? {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
return profile.recent?.accessToken
|
||||
}
|
||||
|
||||
public init(credential: Credential) {
|
||||
self.credential = credential
|
||||
|
||||
// TODO: Profile 을 저장하고 로드하기
|
||||
// 만약 로드한 accessToken 이 유효하면 자동 로그인 성공
|
||||
}
|
||||
|
||||
func setAccessToken(_ accessToken: String, expired: Date) {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
|
||||
profile.recent = Recent(accessToken: accessToken, accessTokenExpired: expired)
|
||||
}
|
||||
|
||||
func resetAccessToken() {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
|
||||
profile.recent = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissAccount {
|
||||
|
||||
public class Profile: Codable {
|
||||
public var recent: Recent?
|
||||
public var loves: [Love]
|
||||
|
||||
init() {
|
||||
recent = nil
|
||||
loves = []
|
||||
}
|
||||
}
|
||||
|
||||
public struct Recent: Codable {
|
||||
public let accessToken: String
|
||||
public let accessTokenExpired: Date
|
||||
}
|
||||
|
||||
public struct Love: Codable {
|
||||
// TODO: write
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
141
KissMe/KissProfile.swift
Normal file
141
KissMe/KissProfile.swift
Normal file
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// KissProfile.swift
|
||||
// KissMe
|
||||
//
|
||||
// Created by ened-book-m1 on 2023/05/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public class KissProfile {
|
||||
|
||||
let profileLock = NSLock()
|
||||
var profile = Profile()
|
||||
|
||||
public var accessToken: String? {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
return profile.recent?.accessToken
|
||||
}
|
||||
|
||||
public var accessTokenExpiredDate: Date? {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
return profile.recent?.accessTokenExpired
|
||||
}
|
||||
|
||||
public var isMock: Bool? {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
return profile.recent?.isMock
|
||||
}
|
||||
|
||||
public init() {
|
||||
loadProfile()
|
||||
}
|
||||
|
||||
func setAccessToken(_ accessToken: String, expired: Date, isMock: Bool) {
|
||||
profileLock.lock()
|
||||
profile.recent = Recent(isMock: isMock, accessToken: accessToken, accessTokenExpired: expired)
|
||||
profileLock.unlock()
|
||||
|
||||
saveProfile()
|
||||
}
|
||||
|
||||
func resetAccessToken() {
|
||||
profileLock.lock()
|
||||
profile.recent = nil
|
||||
profileLock.unlock()
|
||||
|
||||
saveProfile()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissProfile {
|
||||
|
||||
public class Profile: Codable {
|
||||
public var recent: Recent?
|
||||
public var loves: [String: [Love]]
|
||||
|
||||
init() {
|
||||
recent = nil
|
||||
loves = [:]
|
||||
}
|
||||
}
|
||||
|
||||
public struct Recent: Codable {
|
||||
public let isMock: Bool
|
||||
public let accessToken: String
|
||||
public let accessTokenExpired: Date
|
||||
}
|
||||
|
||||
public struct Love: Codable {
|
||||
let key: String
|
||||
let iscd: String
|
||||
let name: String
|
||||
}
|
||||
|
||||
private var profileJsonUrl: URL {
|
||||
URL.currentDirectory().appending(path: "profile.json")
|
||||
}
|
||||
|
||||
public func loadProfile() {
|
||||
do {
|
||||
let data = try Data(contentsOf: profileJsonUrl, options: .uncached)
|
||||
let profile = try JSONDecoder().decode(Profile.self, from: data)
|
||||
self.profile = profile
|
||||
} catch {
|
||||
if (error as NSError).code != 2 {
|
||||
print("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func saveProfile() {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(profile)
|
||||
try data.write(to: profileJsonUrl)
|
||||
} catch {
|
||||
print("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KissProfile {
|
||||
|
||||
public func addLove(_ love: Love, for key: String) {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
|
||||
if let _ = profile.loves[key] {
|
||||
profile.loves[key]?.append(love)
|
||||
}
|
||||
else {
|
||||
profile.loves[key] = [love]
|
||||
}
|
||||
}
|
||||
|
||||
public func removeLove(_ love: Love, for key: String) -> Bool {
|
||||
profileLock.lock()
|
||||
defer {
|
||||
profileLock.unlock()
|
||||
}
|
||||
|
||||
if let index = profile.loves[key]?.firstIndex(where: { $0.iscd == love.iscd }) {
|
||||
profile.loves[key]?.remove(at: index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ extension KissAccount {
|
||||
request.query { result in
|
||||
switch result {
|
||||
case .success(let result):
|
||||
self.setAccessToken(result.accessToken, expired: result.accessTokenExpiredDate)
|
||||
self.setAccessToken(result.accessToken, expired: result.accessTokenExpiredDate, isMock: self.credential.isMock)
|
||||
continuation.resume(returning: true)
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
|
||||
@@ -43,8 +43,9 @@ class KissConsole {
|
||||
case showcase = "showcase"
|
||||
|
||||
// 관심 종목
|
||||
case love = "love" // love nuts.1
|
||||
case hate = "hate" // hate nuts.1
|
||||
case loves = "loves" // 열람
|
||||
case love = "love" // love nuts.1 ISCD
|
||||
case hate = "hate" // hate nuts.1 ISCD
|
||||
|
||||
var needLogin: Bool {
|
||||
switch self {
|
||||
@@ -56,7 +57,7 @@ class KissConsole {
|
||||
return false
|
||||
case .showcase:
|
||||
return false
|
||||
case .love, .hate:
|
||||
case .loves, .love, .hate:
|
||||
return false
|
||||
case .openBag:
|
||||
return false
|
||||
@@ -74,6 +75,7 @@ class KissConsole {
|
||||
|
||||
createSubpath("log")
|
||||
createSubpath("data")
|
||||
lastLogin()
|
||||
|
||||
Task {
|
||||
await onLoadShop()
|
||||
@@ -200,6 +202,22 @@ extension KissConsole {
|
||||
let totalCount = products.reduce(0, { $0 + $1.value.count })
|
||||
print("load products \(totalCount) with \(products.count) key")
|
||||
}
|
||||
|
||||
private func lastLogin() {
|
||||
let profile = KissProfile()
|
||||
guard let isMock = profile.isMock else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
credential = try KissCredential(isMock: isMock)
|
||||
account = KissAccount(credential: credential!)
|
||||
let expiredAt = account?.accessTokenExpiredDate?.yyyyMMdd_HHmmss_forTime ?? ""
|
||||
print("resume \(isMock ? "mock login": "real login") expired at \(expiredAt)")
|
||||
} catch {
|
||||
print("\(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KissConsole {
|
||||
@@ -283,9 +301,7 @@ extension KissConsole {
|
||||
// TODO: work
|
||||
|
||||
do {
|
||||
try await account?.cancelOrder() { result in
|
||||
|
||||
}
|
||||
let success = try await account?.cancelOrder()
|
||||
} catch {
|
||||
print("\(error)")
|
||||
}
|
||||
|
||||
25
README.md
25
README.md
@@ -1,11 +1,22 @@
|
||||
## About KissMe
|
||||
# About KissMe
|
||||
|
||||
KissMe 는 KIS API 를 연동한 swift 라이브러리입니다.
|
||||
|
||||
# KissMeConsole
|
||||
|
||||
KissMeConsole 은 command line 에서 인터렉티브 명령어로 API 호출을 테스트해볼 수 있는 도구입니다.
|
||||
|
||||
KissMeConsole 에서 유효한 command line 명령어는 다음과 같습니다.
|
||||
|
||||
## command list
|
||||
|
||||
|
||||
# KissCredential
|
||||
|
||||
KissCredential 에서 사용하는 json 의 양식은 다음과 같습니다.
|
||||
|
||||
### mock-server.json, real-server.json
|
||||
|
||||
```json
|
||||
{
|
||||
"isMock": false,
|
||||
@@ -19,3 +30,15 @@ KissCredential 에서 사용하는 json 의 양식은 다음과 같습니다.
|
||||
* `accountNo` 에는 계좌번호를 의미합니다. 8-2 형태의 숫자로 입력합니다.
|
||||
* `appKey` 는 한국투자증권 홈페이지에서 발급받은 appkey 입니다.
|
||||
* `appSecret` 는 한국투자증권 홈페이지에서 발급받은 appsecret 입니다.
|
||||
|
||||
### shop-server.json
|
||||
|
||||
```json
|
||||
{
|
||||
"openApiKey": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||
}
|
||||
```
|
||||
|
||||
* `openApiKey` 는 [data.go.kr](https://www.data.go.kr/) 에서 발급받은 API 인증키입니다.
|
||||
* 다음의 Open API 활용신청을 합니다.
|
||||
* [금융위원회_KRX상장종목정보](https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15094775)
|
||||
|
||||
Reference in New Issue
Block a user