Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

427
LINES

< > BotCompany Repo | #1029962 // serveOtherPage2 [gazelle.rocks Include]

JavaX fragment (include)

switchable double bot_maxPollSeconds = 600;
switchable int bot_pollInterval = 500;

Class<? extends Concept> defaultCRUDClass() {
  ret Sequence;
}

O serveOtherPage2(Req req) null {
  new Matches m;

  S uri = dropTrailingSlashIfNemptyAfterwards(req.uri);

  if (eq(uri, "/admin"))
    ret hrefresh(baseLink + "/crud/" + shortClassName(defaultCRUDClass()));

  if (eq(uri, "/latestPost")) {
    UserPost post = highestConceptByField UserPost("created");
    if (post == null) ret serve404("No posts in database");
    ret servePost(post, req);
  }
  
  if (eq(uri, "/latestModifiedPost")) {
    UserPost post = highestConceptByField UserPost("_modified");
    if (post == null) ret serve404("No posts in database");
    ret servePost(post, req);
  }
  
  if (eq(uri, "/rootPosts")) {
    Cl<UserPost> posts = sortedByConceptIDDesc(filter(list(UserPost), post -> empty(post.postRefs)));
    framer().navBeforeTitle = true;
    framer().title = "Root Posts";
    framer().add(ul(lmap postToHTMLWithDate(posts)));
    ret framer().render();
  }
  
  if (eq(uri, "/allPosts")) {
    Cl<UserPost> posts = sortedByConceptIDDesc(list(UserPost));
    framer().navBeforeTitle = true;
    framer().title = "All Posts";
    framer().add(ul(lmap postToHTMLWithDate(posts)));
    ret framer().render();
  }
  
  if (eq(uri, "/latestPosts")) {
    Cl<UserPost> posts = takeFirst(50, sortedByConceptIDDesc(list(UserPost)));
    framer().navBeforeTitle = true;
    framer().title = "Latest Posts & Replies";
    framer().add(ul(lmap postToHTMLWithDate(posts)));
    ret framer().render();
  }
  
  if (eq(uri, "/latestModifiedPosts")) {
    Cl<UserPost> posts = takeFirst(50, sortedByFieldDesc _modified(list(UserPost)));
    framer().navBeforeTitle = true;
    framer().title = "Latest Modified Posts & Replies";
    framer().add(ul(lmap postToHTMLWithDate(posts)));
    ret framer().render();
  }
  
  if (eq(uri, "/mainPosts")) {
    Cl<UserPost> posts = takeFirst(50, sortedByConceptID(conceptsWhereCI UserPost(type := "Main")));
    framer().navBeforeTitle = true;
    framer().title = "Main Posts";
    framer().add(ul(lmap postToHTMLWithDate(posts)));
    ret framer().render();
  }
  
  if (swic(uri, "/html/", m) && isInteger(m.rest())) {
    long id = parseLong(m.rest());
    ret serveHTMLPost(id);
  }
  
  if (swic(uri, "/htmlEmbedded/", m) && isInteger(m.rest())) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    
    HTMLFramer1 framer = framer();
    framer.title = "[" + post.id + "] " + or2(post.title, shorten(post.text));
    framer.add(hcomment("Post begins here") + "\n" + post.text + "\n" + hcomment("Post ends here"));
    
    ret framer.render();
  }
  
  if (swic(uri, "/css/", m) && isInteger(m.rest())) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    ret serveWithContentType(post.text, "text/css");
  }

  if (eq(uri, "/search")) {
    framer().navBeforeTitle = true;
    S q = trim(req.params.get("q"));
    framer().title = joinNemptiesWithColon("Search", q);
    framer().add(hcomment("cookie: " + req.webRequest.cookie()));
    framer().add(hform(hinputfield(+q, autofocus := true) + " " + hsubmit("Search")));
    framer().add(p(small("Search help: If you search for multiple words, they can appear in any order in the text. An underscore matches any character. Plus means space. Post titles, texts and types are searched.")));

    if (nempty(q)) {
      ScoredSearcher<UserPost> searcher = new(q);
      long id = parseLongOpt_pcall(q);
      for (UserPost post)
        searcher.add(post, (post.id == id ? 4 : 0)
          + searcher.score(post.title)*3
          + searcher.score(post.text)*2
          + searcher.score(post.type)
          + searcher.score(joinWithSpace(post.postRefTags))*0.5);
      L<UserPost> posts = searcher!;
      framer().add(p(b(addPlusToCount(searcher.maxResults, l(posts), nPosts(posts))
        + " found for " + htmlEncode2(q))));
      framer().add(ul(lmap postToHTMLWithDate(posts)));
    }
    
    ret framer().render();
  }

  // serve replies for JSTree AJAX call
  if (eq(uri, "/jstree/replies")) {
    UserPost post = getConcept UserPost(parseLong(req.params.get("post")));
    if (post == null) ret serveJSON(ll());
    Cl<UserPost> refs = referencingPosts(post);
    /*[
      { "id" : "demo_root_1", "text" : "Root 1", "children" : true, "type" : "root" },
      { "id" : "demo_root_2", "text" : "Root 2", "type" : "root" }
    ]*/
    ret serveJSON(map(refs, p -> litorderedmap(
      id := p.id,
      text := postToHTMLWithDate(post),
      children := nempty(referencingPosts(p)) ? true : null,
      type := "root" // ?
    )));
  }

  if (startsWith(uri, "/touchPost/", m)) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    if (!canEditPost(post)) ret "You are not authorized to do this";
    touchConcept(post);
    ret hscript([[setTimeout('history.go(-1)', 1000);]]) + "Post " + post.id + " touched";
  }

  if (startsWith(uri, "/hidePost/", m)) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    if (!canEditPost(post)) ret "You are not authorized to do this";
    cset(post, hidden := true);
    ret hrefresh(postLink(post));
  }

  if (startsWith(uri, "/unhidePost/", m)) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    if (!canEditPost(post)) ret "You are not authorized to do this";
    cset(post, hidden := false);
    ret hrefresh(postLink(post));
  }

  if (startsWith(uri, "/markSafe/", m)) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    UserPost codePost = optCast UserPost(first(post.postRefs));
    if (codePost == null) ret "Code post not found";
    if (!canEditPost(codePost)) ret "You are not authorized to do this";
    
    // create "Mark safe" post
    uniqCI UserPost(creator := currentUser(), text := "Mark safe", postRefs := ll(post));
    
    sleepSeconds(6); // allow bot to react

    // touch code post
    touchConcept(codePost);
    ret hrefresh(postLink(codePost));
  }

  if (eq(uri, "/mirrorAllConversations")) {
    if (!req.masterAuthed) ret serveAuthForm(rawLink(uri));
    for (Conversation c)
      rstUpdateMirrorPosts.add(c);
    ret "OK";
  }

  if (startsWith(uri, "/mirrorConversation/", m)) {
    if (!req.masterAuthed) ret serveAuthForm(rawLink(uri));
    long id = parseLong(m.rest());
    Conversation conv = getConcept Conversation(id);
    if (conv == null) ret "Conversation not found";
    conv.updateMirrorPost();
    ret "Mirror post updated (" + htmlEncode2(str(conv.mirrorPost!)) + ")";
  }

  if (eqic(uri, "/favicon.ico"))
    ret serveFavIcon();

  // start of /bot commands

  if (startsWith(uri, "/bot/", m)) {
    req.subURI = m.rest();
    S json = req.params.get("json");
    new Map data;
    if (nempty(json)) data = jsonDecodeMap(json);
    data.putAll(withoutKey("json", req.params));
    
    User user;
    S userName = cast data.get("_user");
    if (nempty(userName)) {
      S botToken = cast data.get("_botToken");
      if (botToken == null) ret serveJSON(error := "Need _botToken");
      user = conceptWhereIC User(name := userName);
      if (user == null) ret serveJSON(error := "User not found");
      if (!eq(botToken, getVar(user.botToken))) ret serveJSON(error := "Wrong bot token");
    } else
      user = user(req);
      
    S function = beforeSlashOrAll(req.subURI);
    req.subURI = substring(req.subURI, l(function)+1);
    req.webRequest.noSpam(); // might want to change this
    
    try object servePossiblyUserlessBotFunction(req, function, data, user);
      
    if (user == null) ret serveJSON(error := "Need _user");

    if (eq(function, "getCookie"))
      ret serveJSON(cookie := req.cookie());
      
    if (eq(function, "authTest"))
      ret serveJSON(status := "You are authorized as " + user.name + " " + roundBracket(user.isMaster ? "master user" : "non-master user"));
      
    if (eq(function, "postCount"))
      ret serveJSON(result := countConcepts(UserPost));      
      
    if (eq(function, "listPosts")) {
      LS fields = unnull(stringToStringListOpt tok_identifiersInOrder(data.get("fields")));
      if (!fields.contains("id")) fields.add(0, "id");
      long changedAfter = toLong(data.get("changedAfter"));
      long repliesTo = toLong(data.get("repliesTo"));
      double pollFor = min(bot_maxPollSeconds, toLong(data.get("pollFor"))); // how long to poll (seconds)
      long startTime = sysNow();

      // We're super-anal about catching all changes. This will probably never trigger
      if (changedAfter > 0 && changedAfter == now()) sleep(1);
      
      Cl<UserPost> posts;
      while true {
        if (repliesTo != 0) {
          posts = referencingPosts(getConcept UserPost(repliesTo));
          if (changedAfter != 0) posts = objectsWhereFieldGreaterThan(posts, _modified := changedAfter);
        } else {
          posts = changedAfter == 0 ? list(UserPost)
            : conceptsWithFieldGreaterThan_sorted(UserPost, _modified := changedAfter);
        }

        // return when there are results, no polling or poll expired
        if (nempty(posts) || pollFor == 0 || elapsedSeconds_sysNow(startTime) >= pollFor)
          ret serveJSON_breakAtLevels(2, result := map(posts, post ->
            mapToValues(fields, field -> getPostFieldForBot(post, field))
          ));

        // sleep and try again
        sleep(bot_pollInterval);
      }
    }

    if (eq(function, "createPost")) {
      Either<O[], S> e = createPostArgs(user, data);
      if (e.isB())
        ret serveJSON(error := e.b());

      Pair<UserPost, Bool> post = uniq2 UserPost(e.a());
      if (!post.b) post.a.bump(); // bump if exact same post exists
      ret serveJSON(status := post.b ? "Post created" : "Post existed already, bumped", postID := post.a.id);
    }

    if (eq(function, "editPost")) {
      long postID = toLong(data.get("postID"));
      UserPost post = getConcept UserPost(postID);
      if (post == null)
        ret serveJSON(error := "Post " + postID + " not found");
        
      Either<O[], S> e = createPostArgs(user, data);
      if (e.isB())
        ret serveJSON(error := e.b());

      int changes = cset(post, e.a());
      // TODO: bump if no changes?
      ret serveJSON(changes > 0 ? "Post updated" : "Post updated, no changes", +postID);
    }

    if (eq(function, "deletePosts")) {
      L<Long> ids = allToLong(tok_integersInOrder((S) data.get("ids")));
      new LS results;
      new LS errors;
      fOr (long id : ids) {
        UserPost post = getConceptOpt UserPost(id);
        if (post == null) errors.add("Post " + id + " not found");
        else {
          if (!user.isMaster && neq(post.creator!, user))
            errors.add("Can't delete post " + id + " from other user");
          else {
            deletePost(post);
            results.add("Post " + id + " deleted");
          }
        }
      }
      ret serveJSON(litorderedmap(+results, +errors));    
    }

    ret serveBotFunction(req, function, data, user);
  }

  if (teamPostID != 0 && eq(uri, "/team"))
    ret serveHTMLPost(teamPostID);

  if (startsWith(uri, "/history/", m)) {
    long id = parseLong(m.rest());
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    ret servePostHistory(post);
  }

  if (startsWith(uri, "/htmlBot/", m)) {
    long id = parseLong(beforeSlashOrAll(m.rest()));
    UserPost post = getConcept UserPost(id);
    if (post == null) ret serve404("Post not found");
    ret doPost(htmlBotURLOnBotServer(id), req.params());
  }

  if (eq(uri, "/deletedPosts")) {
    temp CloseableItIt<S> lines = linesFromFile(deletedPostsFile());
    new L<Map> posts;
    while (lines.hasNext()) {
      S line = lines.next();
      if (isProperlyQuoted(line)) {
        Map map = safeUnstructureMap(unquote(line));
        posts.add(onlyKeys(map, "id", "title", "type"));
      }
    }
    // TODO: show more info, allow restoring posts
    ret serveText("Deleted posts:\n"
      + lines(lmap struct(posts)));
  }

  if (eq(uri, "/formToPost")) {
    User user = currentUser();
    if (user == null) serve500("Please log in first");
    SS params = cloneMap(req.params);
    S type = or2(getAndRemove(params, "_type"), "Form Input");
    S title = getAndRemove(params, "_title");
    L<UserPost> postRefs = map(id -> getConceptOpt UserPost(parseLong(id)), tok_integersInOrder(getAndRemove(params, "_postRefs")));
    LS postRefTags = lines(getAndRemove(params, "_postRefTags"));
    S text = sortLinesAlphaNumIC(mapToLines(params, (k, v) -> urlencode(k) + "=" + urlencode(v)));
    Pair<UserPost,Bool> p = uniq2 UserPost(creator := user,
      +text, +type, +title, +postRefs, +postRefTags);
    ret (p.b ? "Post " + p.a.id + " created" : "Post " + p.a.id + " exists") + hrefresh(2.0, "/" + p.a.id);
  }

  if (eq(uri, "/webPushSubscribe")) {
    MapSO data = jsonDecodeMap(mapGet(req.webRequest.files(), "postData"));
    printVars_str("webPushSubscribe", +data);
    cnew(WebPushSubscription, +data, clientIP := req.webRequest.clientIP());
    ret serveJSON(litmap(message := "success"));
  }
  
  if (eq(uri, "/changePassword")) {
    if (!req.masterAuthed) ret serveAuthForm(rawLink(uri));
    S name = assertNempty(req.get("user"));
    S newPW = assertNempty(req.get("newPW"));
    User user = conceptWhereCI User(+name);
    if (user == null) ret "User not found";
    cset(user, passwordMD5 := SecretValue(hashPW(newPW)));
    ret "PW updated";
  }

  if (eq(uri, "/webPushNotify")) {
    if (!req.masterAuthed) ret serveAuthForm(rawLink(uri));
    S msg = or2(req.params.get("msg"), "Hello user. It is " + localTimeWithSeconds() + " on the server");
    WebPushSubscription sub = getConcept WebPushSubscription(toLong(req.params.get("webPushSubID")));
    if (sub == null) ret serve404("webPushSubID not found");
    
    S mod = dm_require("#1030463/WebPushKeyManager");
    dm_call(mod, "sendNotification", sub.data, msg);
    
    ret "Push message sent";
  }

  if (eq(uri, "/hashPW") && req.masterAuthed)
    ret hashPW(req.params.get("pw"));
} // end of serveOtherPage2

O servePostHistory(UserPost post) {
  ret serveText(unquoteAllLines(loadTextFile(postHistoryFile(post))));
}

// helper for bot functions. return params or error
Either<O[], S> createPostArgs(User user, Map data) {
  S text = cast data.get("text");
  S type = cast data.get("type");
  S title = cast data.get("title");
  S botInfo = or2((S) data.get("botInfo"), "Made by bot");
  LS postRefTags = unnull(lines((S) data.get("refTags")));
  new L<UserPost> postRefs;
  O _refs = data.get("refs");
  if (_refs cast S)
    for (S s : tok_integersInOrder(_refs)) {
      UserPost ref = getConcept UserPost(parseLong(s));
      if (ref == null) ret eitherB("Post " + s + " not found");
      postRefs.add(ref);
    }
  if (empty(text) && empty(title))
    ret eitherB("Need either a text or a title");

  bool isPublic = eqOneOf(data.get("isPublic"), null, true, "1", "t", "true");
  ret eitherA(litparams(creator := user,
    +text, +type, +title, +isPublic, +botInfo, +postRefs, +postRefTags));
}

O serveBotFunction(Req req, S function, Map data, User user) {
  ret serveJSON(error := "You are logged in correctly but function is unknown: " + function);
}

O servePossiblyUserlessBotFunction(Req req, S function, Map data, User user) {
  null;
}

download  show line numbers  debug dex  old transpilations   

Travelled to 5 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, vouqrxazstgt

No comments. add comment

Snippet ID: #1029962
Snippet name: serveOtherPage2 [gazelle.rocks Include]
Eternal ID of this version: #1029962/153
Text MD5: 60137e3ed452a20e0ae7aed04d5139d9
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-10-16 17:18:44
Source code size: 16189 bytes / 427 lines
Pitched / IR pitched: No / No
Views / Downloads: 281 / 2912
Version history: 152 change(s)
Referenced in: #1029963 - gazelle.rocks [working backup before post refs]
#1029968 - gazelle.rocks v2 [abandoned]
#1030607 - DynGazelleRocks