<commit_before>package net.scalytica.symbiotic.models

import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.ScalazReact._
import japgolly.scalajs.react.extra.router.RouterCtl
import net.scalytica.symbiotic.core.http.Failed
import net.scalytica.symbiotic.core.session.Session
import net.scalytica.symbiotic.routing.SymbioticRouter.{Login, ServerBaseURI, View}
import org.scalajs.dom.XMLHttpRequest
import org.scalajs.dom.ext.Ajax
import org.scalajs.dom.raw._
import upickle.default._

import scala.concurrent.Future
import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow

case class Credentials(uname: String, pass: String)

case class User(
  v: Option[VersionStamp] = None,
  id: Option[String] = None,
  username: String,
  email: String,
  password: String,
  name: Option[Name] = None,
  dateOfBirth: Option[String] = None,
  gender: Option[String] = None,
  active: Boolean = true) {

  def readableGender: Option[String] = gender.flatMap {
    case m if m.equals("m") => Some("Male")
    case f if f.equals("f") => Some("Female")
    case _ => None
  }

  def readableName: String =
    name.map { n =>
      s"${n.first.getOrElse("")}${n.middle.map(" " + _).getOrElse("")}${n.last.map(" " + _).getOrElse("")}"
    }.getOrElse(email)
}

object User {

  val empty = User(
    username = "",
    email = "",
    password = ""
  )

  def login(creds: Credentials): Future[XMLHttpRequest] =
    Ajax.post(
      url = s"$ServerBaseURI/login",
      headers = Map(
        "Accept" -> "application/json",
        "Content-Type" -> "application/json"
      ),
      data = s"""{ "username": "${creds.uname}", "password": "${creds.<PASSWORD>}" }"""
    )

  def logout(ctl: RouterCtl[View]): Unit =
    Ajax.get(url = s"$ServerBaseURI/logout").map { xhr =>
      // We don't care what the response status is...we'll remove the cookie anyway
      Session.clear()
      ctl.set(Login).toIO.unsafePerformIO()
    }

  def getUser(uid: String): Future[Either[Failed, User]] =
    Ajax.get(
      url = s"$ServerBaseURI/user/$uid",
      headers = Map(
        "Accept" -> "application/json",
        "Content-Type" -> "application/json"
      )
    ).map { xhr =>
      if (xhr.status >= 200 && xhr.status < 400) {
        val u = read[User](xhr.responseText)
        Right(u)
      }
      else {
        Left(Failed(s"${xhr.status} ${xhr.statusText}: ${xhr.responseText}"))
      }
    }

  def getAvatar(uid: String): Future[Option[Blob]] =
    Ajax.get(url = s"$ServerBaseURI/user/$uid/avatar", responseType = "blob").map { xhr =>
      if (xhr.status >= 200 && xhr.status < 400) {
        Some(xhr.response.asInstanceOf[Blob])
      } else {
        None
      }
    }

  def setAvatar(uid: String, form: HTMLFormElement)(done: Callback): Unit = {
    val fd = new FormData(form)
    val xhr = new XMLHttpRequest
    xhr.onreadystatechange = (e: Event) => {
      if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        done.runNow()
      }
    }
    xhr.open(method = "POST", url = s"$ServerBaseURI/user/$uid/avatar", async = true)
    xhr.send(fd)
  }
}<commit_msg>Small problem with how non-existing avatars were handled...probably need to re-visist all loading of state<commit_after>package net.scalytica.symbiotic.models

import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.ScalazReact._
import japgolly.scalajs.react.extra.router.RouterCtl
import net.scalytica.symbiotic.logger._
import net.scalytica.symbiotic.core.http.Failed
import net.scalytica.symbiotic.core.session.Session
import net.scalytica.symbiotic.routing.SymbioticRouter.{Login, ServerBaseURI, View}
import org.scalajs.dom.XMLHttpRequest
import org.scalajs.dom.ext.Ajax
import org.scalajs.dom.raw._
import upickle.default._

import scala.concurrent.Future
import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow

case class Credentials(uname: String, pass: String)

case class User(
  v: Option[VersionStamp] = None,
  id: Option[String] = None,
  username: String,
  email: String,
  password: String,
  name: Option[Name] = None,
  dateOfBirth: Option[String] = None,
  gender: Option[String] = None,
  active: Boolean = true) {

  def readableGender: Option[String] = gender.flatMap {
    case m if m.equals("m") => Some("Male")
    case f if f.equals("f") => Some("Female")
    case _ => None
  }

  def readableName: String =
    name.map { n =>
      s"${n.first.getOrElse("")}${n.middle.map(" " + _).getOrElse("")}${n.last.map(" " + _).getOrElse("")}"
    }.getOrElse(email)
}

object User {

  val empty = User(
    username = "",
    email = "",
    password = ""
  )

  def login(creds: Credentials): Future[XMLHttpRequest] =
    Ajax.post(
      url = s"$ServerBaseURI/login",
      headers = Map(
        "Accept" -> "application/json",
        "Content-Type" -> "application/json"
      ),
      data = s"""{ "username": "${creds.uname}", "password": "${<PASSWORD>}" }"""
    )

  def logout(ctl: RouterCtl[View]): Unit =
    Ajax.get(url = s"$ServerBaseURI/logout").map { xhr =>
      // We don't care what the response status is...we'll remove the cookie anyway
      Session.clear()
      ctl.set(Login).toIO.unsafePerformIO()
    }

  def getUser(uid: String): Future[Either[Failed, User]] =
    for {
      xhr <- Ajax.get(
        url = s"$ServerBaseURI/user/$uid",
        headers = Map(
          "Accept" -> "application/json",
          "Content-Type" -> "application/json"
        )
      )
    } yield {
      xhr.status match {
        case ok: Int if ok == 200 =>
          val u = read[User](xhr.responseText)
          Right(u)
        case ko =>
          log.warn(s"User $uid could not be found")
          Left(Failed(s"${xhr.status} ${xhr.statusText}: ${xhr.responseText}"))
      }
    }

  def getAvatar(uid: String): Future[Option[Blob]] =
    (for {
      xhr <- Ajax.get(url = s"$ServerBaseURI/user/$uid/avatar", responseType = "blob")
    } yield {
      xhr.status match {
        case ok: Int if ok == 200 =>
          Some(xhr.response.asInstanceOf[Blob])
        case ko =>
          log.info(s"Unrecognized status code $ko from getAvatar service.")
          None
      }
    }).recoverWith {
      case err =>
        log.info("User has not uploaded an Avatar image")
        Future.successful(None)
    }

  def setAvatar(uid: String, form: HTMLFormElement)(done: Callback): Unit = {
    val fd = new FormData(form)
    val xhr = new XMLHttpRequest
    xhr.onreadystatechange = (e: Event) => {
      if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        done.runNow()
      }
    }
    xhr.open(method = "POST", url = s"$ServerBaseURI/user/$uid/avatar", async = true)
    xhr.send(fd)
  }
}
{'fraction_non_alphanumeric': 0.15212527964205816, 'fraction_numerical': 0.0035794183445190158, 'mean_word_length': 2.9377568995889605, 'pattern_counts': {'":': 4, '<': 9, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7149803', 'n_tokens_mistral': 2363, 'n_tokens_neox': 2287, 'n_words': 473}