Accessing database in a command - vapor

I want to create a command in which you can create a user (like database seeds).
However, I cannot access a database in a command, my code is like the following:
import Command
import Crypto
struct CreateUserCommand: Command {
var arguments: [CommandArgument] {
return [.argument(name: "email")]
}
var options: [CommandOption] {
return [
.value(name: "password", short: "p", default: "", help: ["Password of a user"]),
]
}
var help: [String] {
return ["Create a user with provided identities."]
}
func run(using context: CommandContext) throws -> Future<Void> {
let email = try context.argument("email")
let password = try context.requireOption("password")
let passwordHash = try BCrypt.hash(password)
let user = User(email: email, password: password)
return user.save(on: context.container).map(to: Future<Void>) { user in
return .done(on: context.container)
}
}
}
Like the above, I want to save users by executing a query on context.container, but I got argument type 'Container' does not conform to expected type 'DatabaseConnectable' Error.
How to access to the database in a command?

It seems like this might be the way to go:
func run(using context: CommandContext) throws -> EventLoopFuture<Void> {
let email = try context.argument("email")
let password = try context.requireOption("password")
let passwordHash = try BCrypt.hash(password)
let user = User(email: email, password: password)
return context.container.withNewConnection(to: .psql) { db in
return user.save(on: db).transform(to: ())
}
}

Related

Organizing groups in Firebase Authentication on Swift

I am using Firebase authentication on Swift in Xcode. I want to create "groups" for the user login so that certain users will have access to certain data. For example, in my app, I want basketball players on the basketball team to only have access to the basketball stats. Does anyone know what this is called in Firebase and how to do it?
As mentioned, the user for the authentication (Auth user) is just the user for the authentication, it does not contain much more information. Please see the attached screenshot from Firebase (Authentication):
That is the reason why we have to add a new User struct (in the users collection) which provides all these kind of information (could be name, age, groups of something.... whatever). The user document needs a reference to the Auth user. In the example I am using the user uid (#frank-van-puffelen is that a common way to use the uid or does it cause safety relevant issues?)
One side note, since we only get the entire documents and sometimes a user could have some private data that must not be available for others, it may make sense to split the struct into PublicUser and PrivateUser.
Anyway, for this example, let's create a User struct in swift
User
//
// User.swift
// Firebase User
//
// Created by Sebastian Fox on 18.08.22.
//
import Foundation
import SwiftUI
import Firebase
struct User: Codable, Identifiable, Hashable {
var id: String?
var name: String
var group: SportType
init(name: String, group: SportType, id: String?) {
self.id = id
self.name = name
self.group = group
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let name = data["name"] as? String else {
return nil
}
guard let group = data["group"] as? SportType else {
return nil
}
id = document.documentID
self.name = name
self.group = group
}
enum CodingKeys: String, CodingKey {
case id
case name
case group
}
}
extension User: Comparable {
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id
}
static func < (lhs: User, rhs: User) -> Bool {
return lhs.name < rhs.name
}
}
// I also create an enum with sort types, this is not necessarily part of the User struct. To load it to the Firestone database it must be codable.
enum SportType: String, Codable, CaseIterable {
case basektball = "Basketball"
case baseball = "Baseball"
case soccer = "Soccer"
case chess = "Chess"
case noSport = "No Sport"
}
Now, let's do the magic with the UserViewModel which contains the functions we call to work with Firebase (Firestore), e.g. signUp (here we are talking about the Auth user), signIn (Auth user again) or createNewUser (here it is our new user struct):
UserViewModel.swift
//
// UserViewModel.swift
// Firebase User
//
// Created by Sebastian Fox on 18.08.22.
//
import Foundation
import FirebaseFirestore
import Firebase
import FirebaseFirestoreSwift
class UsersViewModel: ObservableObject {
let db = Firestore.firestore()
// Published and saved to local device
#Published var users = [User]()
// Sign Up
func signUp(email: String, password: String, completion: #escaping (Bool, String)->Void) {
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
// ERROR AND SUCCESS HANDLING
if error != nil {
// ERROR HANDLING
print(error?.localizedDescription as Any)
completion(false, "ERROR")
}
// SUCCESS HANDLING
completion(true, authResult?.user.uid ?? "")
}
}
// Sign In
func signIn(email: String, password: String, completion: #escaping (Bool)->Void) {
Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
// ERROR AND SUCCESS HANDLING
if error != nil {
// ERROR HANDLING
print(error?.localizedDescription as Any)
completion(true)
}
// SUCCESS HANDLING
completion(true)
}
}
// Sign Out
func signOut() {
try! Auth.auth().signOut()
}
// Create new user
func createNewUser(name: String, group: SportType, id: String) {
do {
let newUser = User(name: name, group: group, id: id)
try db.collection("users").document(newUser.id!).setData(from: newUser) { _ in
print("User \(name) created")
}
} catch let error {
print("Error writing user to Firestore: \(error)")
}
}
// FOR TESTING: Get a list of all users
func fetchAllUsers(_ completion: #escaping (Bool) ->Void) {
self.users = []
db.collection("users").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.users = documents.map { queryDocumentSnapshot -> User in
let data = queryDocumentSnapshot.data()
let id = data["id"] as? String ?? ""
let name = data["name"] as? String ?? ""
let group = data["group"] as? String ?? ""
return User(name: name, group: SportType(rawValue: group) ?? .noSport, id: id)
}
completion(true)
}
}
}
Now you have 2 options to signUp (the Auth user) AND create a new user (based on the new user struct):
You have 2 separate views, on the first view, the user signs up, that creates the Auth user. On the second view, which is only available after signing up, the user can add data like name, group or whatever you want. (I'd prefer this option)
You handle everything in one view. You are holding all necessary data, call the signUp function and when you get the completion response, you call the function to create the user.
One last thing, since you don't get that information from Auth.auth(), if you want to be able to change these data, you'll have to fetch the user data for that specific user from the Firestore database. Of course you can save these information as values to UserDefaults (storage) while you create a new user, later you can save that information when the user logs in.
Best, Sebastian

Vapor - updating user property returns "Precondition failed - id.exists"

I've tried to add UUID to UUID array property on users model but it returns "Precondition failed - id.exists". I'm using update on database not create or save. Using PostgresSQL for database. The error is on FluentKit -> Model -> Model+CRUD.swift -> _ update(on: ) line 43. Attaching the code below.
User model:
final class AppUser: Model, Content {
static let schema: String = "users"
#ID(key: .id)
var id: UUID?
#Field(key: "email")
var email: String
#Field(key: "passwordHash")
var passwordHash: String
#Field(key: "plantIds")
var plantIds: [UUID]
#Field(key: "sharedPlantIds")
var sharedPlantIds: [UUID]
init() {}
init(id: UUID? = nil, email: String, passwordHash: String, plantIds: [UUID] = [UUID](), sharedPlantIds: [UUID] = [UUID]()){
self.id = id
self.email = email
self.passwordHash = passwordHash
self.plantIds = plantIds
self.sharedPlantIds = sharedPlantIds
}
}
Request:
func addOwnPlant(req: Request) throws -> EventLoopFuture<Response> {
let user = try req.auth.require(AppUser.self)
let reqPlant = try req.content.decode(AppUserPlantCreateRequest.self)
let uuid = UUID()
print(uuid)
let newPlant = AppUserPlant(id: uuid, parentId: reqPlant.parentId, notes: reqPlant.notes, timesPlantIsWatered: reqPlant.timesPlantIsWatered, name: reqPlant.name, lastTimeWatered: reqPlant.lastTimeWatered)
return newPlant.save(on: req.db).flatMap { _ in
guard let id = newPlant.id else {
return DataWrapper.encodeResponse(data: Fail.init(message: "no id"), for: req)
}
user.plantIds.append(id)
return user.update(on: req.db).flatMap {
return DataWrapper.encodeResponse(data: newPlant.newPlantResposne, for: req)
}
}
}
Migration:
struct AppUserMigration: Migration {
var name: String {"Users migration"}
func prepare(on database: Database) -> EventLoopFuture<Void> {
return database.schema("users")
.field("id", .uuid)
.field("email", .string, .required)
.field("passwordHash",.string,.required)
.field("plantIds", .array(of: .uuid))
.field("sharedPlantIds", .array(of: .uuid))
.unique(on: "email")
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema("users").delete()
}
}
I found out why that issue is coming up. I was taking the user from the jwt like that - let user = try req.auth.require(AppUser.self) and after that I was modifying it and saving it to the database. What i did was to fetch the user from the database, modify it and after that save it to the database. It was saying that i can't update or save the user when I used the first option because the user from the jwt was like brand new and postgres was acting crazy because there was already a user with this id.

Login credentials missing in swift UserDefaults

I have an application which uses a rest api for authentication. The problem I am facing now is that I save user's token in my UserDefaults and username too because those are the two main parameters needed to get user details. so if the application is closed by the user he should still be able to view the view his profile when he opens the application back but instead the profile returns empty details. this is the UserDefaults codes that I have
let defaults = UserDefaults.standard
var isLoggedIn : Bool {
get {
return defaults.bool(forKey: LOGGED_IN_KEY)
}
set {
defaults.set(newValue, forKey: LOGGED_IN_KEY)
}
}
//Auth Token
var authToken: String {
get {
return defaults.value(forKey: TOKEN_KEY) as? String ?? ""
}
set {
defaults.set(newValue, forKey: TOKEN_KEY)
}
}
var userUsername: String {
get {
return defaults.value(forKey: USERNAME_KEY) as? String ?? ""
}
set {
defaults.set(newValue, forKey: USERNAME_KEY)
}
}
I have no idea why it isnt retrieving the user data.
My second question is when I logout the user, all the users details are cleared as expected but the moment I try loging in with a different user, the new user's authToken and details gets printed in the console but the user profile returns the profile of the previous person. which is not supposed to be. my code is shown below
func logoutUser() -> Void {
pk = 0
username = ""
email = ""
firstName = ""
lastName = ""
AuthService.instance.isLoggedIn = false
AuthService.instance.authToken = ""
AuthService.instance.userUsername = ""
}
#IBAction func logoutPressed(_ sender: Any) {
UserDataService.instance.logoutUser()
dismiss(animated: true, completion: nil)
}
I would also like to add that when i run the api using postman i get a response that "detail": "Signature has expired." so i had to input the new token in the header so it displays the user details again
enum SettingKeys: String {
case authToken
//...
}
struct Settings {
static var authToken: String? {
get { return UserDefaults.standard.string(forKey: SettingKeys.authToken.rawValue) }
set(value) { UserDefaults.standard.set(value, forKey: SettingKeys.authToken.rawValue) }
}
static func deleteAll(exclude: [SettingKeys] = []) {
let saveKeys = exclude.map({ $0.rawValue })
for key in UserDefaults.standard.dictionaryRepresentation().keys {
if !saveKeys.contains(key) {
UserDefaults.standard.removeObject(forKey: key)
}
}
}
}
I recommend storing keys as Enum, because then u can use it like that:
//Read
if let token = Settings.authToken {
//do something
}
//Write
Settings.authToken = "123456"
//Delete settings
Settings.deleteAll()
//or choose what to leave
Settings.deleteAll(exclude: [.authToken])
And it's worth to mention that defaults.synchronize() is deprecated.

create user with xmppframework

I'm currently trying to setup a chat functionality by using ejabberd
I'm using the xmppframework to communicate with my ejabberd server.
What I'm trying to do is the following.
#IBAction func registerUser(_ sender: Any) {
var username: String = ""
var password: String = ""
let elements: NSMutableArray = []
if let user = tf_user.text {
username = "\(user)#192.168.1.19"
elements.add(XMLElement(name: "username", stringValue: username))
}
if let pass = tf_password.text {
password = pass
elements.add(XMLElement(name: "password", stringValue: password))
}
if username != "" && password != "" {
if stream.supportsInBandRegistration() {
do {
try stream.register(withElements: elements as! [Any])
} catch let error {
print(error)
}
}
}
}
However when I make the register call I get the following error
<iq xmlns="jabber:client" from="192.168.1.19" type="error">
<query xmlns="jabber:iq:register">
<username>user1#192.168.1.19</username>
<password>pass</password>
</query><error code="400" type="modify">
<bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Malformed username</text></error></iq>
I'm not sure why this isn't working. Does anyone know where I'm going wrong? I feel like it has something to do with adding my element.

iOS Swift - SharkORM won't commit

I'm using SharkORM on iOS Swift project and I'm having problem with a specific object. I have other objects in the project that works, but this one.
My class is like this:
import Foundation
import SharkORM
class Exam: SRKObject {
dynamic var serverId: NSNumber?
dynamic var type: String?
dynamic var when: String?
dynamic var file: String?
dynamic var filename: String?
dynamic var url: String?
func toJson() -> [String:Any?] {
return [
"name" : type,
"date" : when,
"serverId" : serverId,
"file" : file,
"filename" : filename,
"url" : url,
"id" : id
]
}
static func fromJson(_ json: [String:Any?]) -> Exam {
let exam = Exam()
exam.id = json["id"] as? NSNumber ?? NSNumber(value: 0)
exam.type = json["name"] as? String ?? ""
exam.file = json["file"] as? String ?? ""
exam.filename = json["filename"] as? String ?? ""
exam.url = json["url"] as? String ?? ""
exam.serverId = json["serverId"] as? NSNumber ?? NSNumber(value: 0)
exam.when = json["date"] as? String ?? ""
return exam
}
}
I add to an array objects that needs to be saved and after user press save button, the app starts committing it.
// save exams
for exam in self.examsToSave {
if !exam.commit() {
print("Error commiting exam.")
}
}
if let rs = Exam.query().fetch() {
print("exams: \(rs.count)")
}
The commit method returns true and I added a print right after it finishes committing and result is zero.
Any idea?
I found out the problem right after post it. In my text here, my variable "when" was colored like a keyword. I just changed the name to "whenDate" and it started committing. Weird it didn't show up any error or a crash. Anyway, a variable named "when" is not allowed inside a SRKObject.
Given same Commit problem, figured best to keep to topic here. And I've spent number of hours trying to debug this so thought I'd try this:
I have a simple class (and overly simplified but tested as provided here):
class user: SRKObject {
#objc dynamic var name: String = ""
}
(No, no odd syntax coloring on the object property names.)
And I do the following (simplified test case), first defining
public var currUser = user()
Then in a function:
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser.name)")
} else {
self.currUser.name = "T1 User"
if !self.currUser.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u.count)")
}
}
The commit() call succeeds -- at least I don't get the "Failed to commit" message. However, I do get zero count in the last fetch().
Viewing the DB file (in Simulator) from a "DB Browser for SQLite" shows the DB is created fine but the "user" record is not in there, and neither is the "committed" data.
BTW when I had this code in SRKTransaction.transaction, it DID fall into the failure (rollback) block, so yes, did get a transaction error, but tracking that down will be next.
In the meantime, appreciate in advance any help given this case as presented should work.
#retd111, I copied and pasted your code and got the same error.
Then, I moved the currUser to a local var, like this:
override func viewDidLoad() {
super.viewDidLoad()
var currUser: user? = nil
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser!.name)")
} else {
currUser = user()
currUser!.name = "T1 User"
if !currUser!.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u?.count ?? 0)")
}
}
}
It works without problems.
For some reason, if you instantiate the currUser as a class member variable, as your example:
public var currUser = user()
it won't work.

Resources