<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://wiki.socialstack.dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Luke</id>
	<title>SocialStack - User contributions [en-gb]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.socialstack.dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Luke"/>
	<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Special:Contributions/Luke"/>
	<updated>2026-10-16T01:04:40Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.36.2</generator>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=794</id>
		<title>Migration Framework</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=794"/>
		<updated>2026-01-24T12:24:23Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Compatibility */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;If you&amp;#039;d like to move your data in either direction between any database engine (currently 3) supported by Socialstack, there is a built in migration mechanism at /v1/migration/migrate. This mechanism is best used for sidegrades rather than up/downgrading.&lt;br /&gt;
&lt;br /&gt;
The following example assumes MySQL to MongoDB.&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
&lt;br /&gt;
You first need to have installed Api/DatabaseMySQL and Api/DatabaseMongoDB in to your project. In your appsettings.json, you then connect to both:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;ConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;server=localhost;port=3306;SslMode=none;AllowPublicKeyRetrieval=true;database=X;user=Y;password=Z&amp;quot;&lt;br /&gt;
},&lt;br /&gt;
&amp;quot;MongoConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;IsPrimary&amp;quot;: true,&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;mongodb://localhost:27017/X?ssl=false&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mark the target database engine as IsPrimary: true. Start it up and you should see &amp;quot;MySQL is installed and configured but not mounting as it is not the primary DBE&amp;quot; appear in the console.&lt;br /&gt;
&lt;br /&gt;
From here, login as the admin account as normal (using details from the primary database, Mongo in this case). Then, load the migrate URL:&lt;br /&gt;
&lt;br /&gt;
/v1/migration/migrate?from=mysql&amp;amp;to=mongodb&lt;br /&gt;
&lt;br /&gt;
The files for this are in Api/Database/Migration/ - it is recommended to look over that to understand how it works and what it will do. Importantly, it does not delete anything at any point by default, it essentially just loads objects from one database engine and creates them in the other, transferring the ID&amp;#039;s as-is. You may want to delete any generated contents of your target database in between starting up the site and running this.&lt;br /&gt;
&lt;br /&gt;
= Compatibility =&lt;br /&gt;
&lt;br /&gt;
Due to the copy transfer mechanism, note that it does not passively upgrade or downgrade. Most things are cross-compatible through a wide range of socialstack versions, however there are two notable compatibility points: &lt;br /&gt;
&lt;br /&gt;
* Any mappings from core 2024 and earlier would be skipped. This is because in core 2025 and onwards, they changed to being inline in rows/documents instead.&lt;br /&gt;
&lt;br /&gt;
* Permalinks did not exist in core 2024 and earlier. There is a custom built routing engine inside core 2025 which made automated link preservation possible, complex multi-directory slugs as well as removing the dependency on having a reverse proxy (you can run core 2026 onwards without NGINX, redirect rules included). In a core 2024 -&amp;gt; 2025+ migration, the URLs on pages will implicitly be dropped. The page structure itself is the same however, albeit a core 2025+ project defaults to generating pages with wrapping templates.&lt;br /&gt;
&lt;br /&gt;
If you need any assistance with generating permalinks or migrating mappings as well please do ask! In short you can see the target format if you allow core 2025+ to startup and generate its default data, and then look at e.g. the site_page table. In there is a JSON column called Mappings which holds that data instead. For permalinks, it is site_permalink where you will see a relatively simple source URL and target locator, usually of the form &amp;quot;page:X&amp;quot; where X is an ID.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=793</id>
		<title>Migration Framework</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=793"/>
		<updated>2026-01-24T12:23:33Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Compatibility */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;If you&amp;#039;d like to move your data in either direction between any database engine (currently 3) supported by Socialstack, there is a built in migration mechanism at /v1/migration/migrate. This mechanism is best used for sidegrades rather than up/downgrading.&lt;br /&gt;
&lt;br /&gt;
The following example assumes MySQL to MongoDB.&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
&lt;br /&gt;
You first need to have installed Api/DatabaseMySQL and Api/DatabaseMongoDB in to your project. In your appsettings.json, you then connect to both:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;ConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;server=localhost;port=3306;SslMode=none;AllowPublicKeyRetrieval=true;database=X;user=Y;password=Z&amp;quot;&lt;br /&gt;
},&lt;br /&gt;
&amp;quot;MongoConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;IsPrimary&amp;quot;: true,&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;mongodb://localhost:27017/X?ssl=false&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mark the target database engine as IsPrimary: true. Start it up and you should see &amp;quot;MySQL is installed and configured but not mounting as it is not the primary DBE&amp;quot; appear in the console.&lt;br /&gt;
&lt;br /&gt;
From here, login as the admin account as normal (using details from the primary database, Mongo in this case). Then, load the migrate URL:&lt;br /&gt;
&lt;br /&gt;
/v1/migration/migrate?from=mysql&amp;amp;to=mongodb&lt;br /&gt;
&lt;br /&gt;
The files for this are in Api/Database/Migration/ - it is recommended to look over that to understand how it works and what it will do. Importantly, it does not delete anything at any point by default, it essentially just loads objects from one database engine and creates them in the other, transferring the ID&amp;#039;s as-is. You may want to delete any generated contents of your target database in between starting up the site and running this.&lt;br /&gt;
&lt;br /&gt;
= Compatibility =&lt;br /&gt;
&lt;br /&gt;
Due to the copy transfer mechanism, note that it does not passively upgrade or downgrade. Most things are cross-compatible through a wide range of socialstack versions, however there are two notable compatibility points: &lt;br /&gt;
&lt;br /&gt;
* Any mappings from core 2024 and earlier would be skipped. This is because in core 2025 and onwards, they changed to being inline in rows/documents instead.&lt;br /&gt;
&lt;br /&gt;
* Permalinks did not exist in core 2024 and earlier. There is a custom built routing engine inside core 2025 which made automated link preservation possible, complex multi-directory slugs as well as removing the dependency on having a reverse proxy (you can run core 2026 onwards without NGINX, redirect rules included). In a core 2024 -&amp;gt; 2025+ migration, the URLs on pages will implicitly be dropped. The page structure itself is the same however.&lt;br /&gt;
&lt;br /&gt;
If you need any assistance with generating permalinks or migrating mappings as well please do ask! In short you can see the target format if you allow core 2025+ to startup and generate its default data, and then look at e.g. the site_page table. In there is a JSON column called Mappings which holds that data instead. For permalinks, it is site_permalink where you will see a relatively simple source URL and target locator, usually of the form &amp;quot;page:X&amp;quot; where X is an ID.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=792</id>
		<title>Migration Framework</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Migration_Framework&amp;diff=792"/>
		<updated>2026-01-24T12:21:59Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;If you&amp;#039;d like to move your data in either direction between any database engine (currently 3) supported by Socialstack, there is a built in migration mechanism at /v1/migratio...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;If you&amp;#039;d like to move your data in either direction between any database engine (currently 3) supported by Socialstack, there is a built in migration mechanism at /v1/migration/migrate. This mechanism is best used for sidegrades rather than up/downgrading.&lt;br /&gt;
&lt;br /&gt;
The following example assumes MySQL to MongoDB.&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
&lt;br /&gt;
You first need to have installed Api/DatabaseMySQL and Api/DatabaseMongoDB in to your project. In your appsettings.json, you then connect to both:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;ConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;server=localhost;port=3306;SslMode=none;AllowPublicKeyRetrieval=true;database=X;user=Y;password=Z&amp;quot;&lt;br /&gt;
},&lt;br /&gt;
&amp;quot;MongoConnectionStrings&amp;quot;: {&lt;br /&gt;
  &amp;quot;IsPrimary&amp;quot;: true,&lt;br /&gt;
  &amp;quot;DefaultConnection&amp;quot;: &amp;quot;mongodb://localhost:27017/X?ssl=false&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mark the target database engine as IsPrimary: true. Start it up and you should see &amp;quot;MySQL is installed and configured but not mounting as it is not the primary DBE&amp;quot; appear in the console.&lt;br /&gt;
&lt;br /&gt;
From here, login as the admin account as normal (using details from the primary database, Mongo in this case). Then, load the migrate URL:&lt;br /&gt;
&lt;br /&gt;
/v1/migration/migrate?from=mysql&amp;amp;to=mongodb&lt;br /&gt;
&lt;br /&gt;
The files for this are in Api/Database/Migration/ - it is recommended to look over that to understand how it works and what it will do. Importantly, it does not delete anything at any point by default, it essentially just loads objects from one database engine and creates them in the other, transferring the ID&amp;#039;s as-is. You may want to delete any generated contents of your target database in between starting up the site and running this.&lt;br /&gt;
&lt;br /&gt;
= Compatibility =&lt;br /&gt;
&lt;br /&gt;
Due to the copy transfer mechanism, note that it does not passively upgrade or downgrade. Most things are cross-compatible through a wide range of socialstack versions, however there are two notable compatibility points: &lt;br /&gt;
&lt;br /&gt;
* Any mappings from core 2024 and earlier would be skipped. This is because in core 2025 and onwards, they changed to being inline in rows/documents instead. Similarly you would also be missing &lt;br /&gt;
&lt;br /&gt;
* Permalinks did not exist in core 2024 and earlier. There is a custom built routing engine inside core 2025 which made automated link preservation possible, complex multi-directory slugs as well as removing the dependency on having a reverse proxy (you can run core 2026 onwards without NGINX, redirect rules included). In a core 2024 -&amp;gt; 2025+ migration, the URLs on pages will implicitly be dropped. The page structure itself is the same however.&lt;br /&gt;
&lt;br /&gt;
If you need any assistance with generating permalinks or migrating mappings as well please do ask! In short you can see the target format if you allow core 2025+ to startup and generate its default data, and then look at e.g. the site_page table. In there is a JSON column called Mappings which holds that data instead. For permalinks, it is site_permalink where you will see a relatively simple source URL and target locator, usually of the form &amp;quot;page:X&amp;quot; where X is an ID.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=567</id>
		<title>API Caching</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=567"/>
		<updated>2023-08-30T19:35:57Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;It&amp;#039;s often the case that you have at least a few [[entities]] which are accessed much more often than they are updated. Typically a site&amp;#039;s throughput is limited by the database so the less database traffic that happens the higher that throughput will be. SocialStack has a built in caching mechanism which allows you to mark an entity as cached. This means the list of all entities of that type is obtained once during server startup and then kept up to date through the [[ContentSync|internal server mesh network]].&lt;br /&gt;
&lt;br /&gt;
Caching therefore works best if you cache things accessed frequently but changed rarely. As cached objects are stored in your web servers memory, be careful not to cache something huge such as a type with 10M+ rows unless you have the RAM to spare. Some built in examples are site configuration, locales and the pages of your site. These are all cached by default.&lt;br /&gt;
&lt;br /&gt;
= Usage =&lt;br /&gt;
&lt;br /&gt;
To make an entity use the cache, simply call Cache(); from within the service constructor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public ThingService() {&lt;br /&gt;
&lt;br /&gt;
    // &amp;quot;Thing&amp;quot; entities will be cached.&lt;br /&gt;
    Cache();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it is a thirdparty service and you would like to avoid editing its source to allow for easier updates, you can also call Cache(); via the Events.Service.AfterStart event.&lt;br /&gt;
&lt;br /&gt;
= Functionality =&lt;br /&gt;
&lt;br /&gt;
The cache reuses objects whenever possible so when you ask a caching service to get or list objects then little to no allocations actually happen. These calls also return instantaneously as the object is coming from an already loaded in-memory data structure. In the example above, if you ask &amp;quot;ThingService&amp;quot; to get object with ID #14, that returned object is returned immediately, comes from the cache and is also reused.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Because the object is reused, avoid changing its fields anywhere other than via Update&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// private ThingService _thingService;&lt;br /&gt;
&lt;br /&gt;
// This returns instantly with a reused object.&lt;br /&gt;
var thing14 = await _thingService.Get(context, 14);&lt;br /&gt;
&lt;br /&gt;
// Because it&amp;#039;s reused, do not edit fields!&lt;br /&gt;
// thing14.Name = &amp;quot;do not do this&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// The only correct way to update fields is via the usual update call:&lt;br /&gt;
thing14 = await _thingService.Update(context, thing14, (Context c, Thing updateable) =&amp;gt; {&lt;br /&gt;
    updateable.Name = &amp;quot;Updated Name&amp;quot;;&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
&lt;br /&gt;
Because cached services are not depending on the database and do not allocate, they have extremely high throughput. You can use service Get hundreds of millions of times per second without risking long GC pauses (not that you would ever put this much traffic on one server though!). The JSON serialisation process also avoids as many allocations as possible and can also return more than 10 million results per second via a websocket. The limiting factor is usually the network bandwidth.&lt;br /&gt;
&lt;br /&gt;
Note that the use of the websocket is important if you are expecting this level of throughput being necessary. That is because the context needs to collect the user information which usually involves the database (the user service is rarely set to being cached, but you can do that too) and this context load only occurs once on a websocket.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
Because caching a service is primarily done for performance, enabling the cache also makes a service switch ID assignment model. This is covered in more detail on the [[ContentSync|clustering mechanics page]].&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=566</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=566"/>
		<updated>2023-08-30T19:27:18Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* AWS Elastic Loadbalancing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
== AWS Elastic Load Balancing ==&lt;br /&gt;
&lt;br /&gt;
Because SocialStack servers just need to exist and point at your database to start taking load and working together, SocialStack naturally works well with ELB. There is just one very important note: When ELB destroys and recreates servers, you need to make sure your hostnames are based on reused values. For example, if you have 3 servers in your ELB pool, their hostnames are always 1, 2 and 3. If 2 dies, the new replacement node gains hostname 2. This avoids your ClusteredServer table growing with hundreds of entries of dead servers. That is mostly only problematic for the block ID assignment mentioned below as it results in large chunks of your ID space becoming permanently unusable otherwise.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
As a result, alternate methods are also present for the purpose of solving these problems. Most sites however won&amp;#039;t actually encounter this as an issue.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;br /&gt;
&lt;br /&gt;
An example of an entity that uses low frequency block assignment is locales. Locales are edited rarely so they are cached meaning block assignment is active, but they also benefit greatly from having compact close together IDs due to the way how many SocialsStack features internally optimise localised sets. So, by setting it to low frequency block assignment the locales will end up tightly next to each other in the ID space - even if the database increment value is incorrect.&lt;br /&gt;
&lt;br /&gt;
Because of this methods simplcitity (&amp;quot;latest plus one&amp;quot;) - it is vulnerable to ID collisions if the object is actually created at a high frequency and the servers knowledge of the latest object is simply fractionally out of date.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=565</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=565"/>
		<updated>2023-08-30T19:24:02Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* How ContentSync works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
== AWS Elastic Loadbalancing ==&lt;br /&gt;
&lt;br /&gt;
Because SocialStack servers just need to exist and point at your database to start taking load and working together, SocialStack naturally works well with ELB. There is just one very important note: When ELB destroys and recreates servers, you need to make sure your hostnames are based on reused values. For example, if you have 3 servers in your ELB pool, their hostnames are always 1, 2 and 3. If 2 dies, the new replacement node gains hostname 2. This avoids your ClusteredServer table growing with hundreds of entries of dead servers. That is mostly only problematic for the block ID assignment mentioned below as it results in large chunks of your ID space becoming permanently unusable otherwise.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
As a result, alternate methods are also present for the purpose of solving these problems. Most sites however won&amp;#039;t actually encounter this as an issue.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;br /&gt;
&lt;br /&gt;
An example of an entity that uses low frequency block assignment is locales. Locales are edited rarely so they are cached meaning block assignment is active, but they also benefit greatly from having compact close together IDs due to the way how many SocialsStack features internally optimise localised sets. So, by setting it to low frequency block assignment the locales will end up tightly next to each other in the ID space - even if the database increment value is incorrect.&lt;br /&gt;
&lt;br /&gt;
Because of this methods simplcitity (&amp;quot;latest plus one&amp;quot;) - it is vulnerable to ID collisions if the object is actually created at a high frequency and the servers knowledge of the latest object is simply fractionally out of date.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=564</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=564"/>
		<updated>2023-08-30T19:05:45Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Low frequency block assignment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
As a result, alternate methods are also present for the purpose of solving these problems. Most sites however won&amp;#039;t actually encounter this as an issue.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;br /&gt;
&lt;br /&gt;
An example of an entity that uses low frequency block assignment is locales. Locales are edited rarely so they are cached meaning block assignment is active, but they also benefit greatly from having compact close together IDs due to the way how many SocialsStack features internally optimise localised sets. So, by setting it to low frequency block assignment the locales will end up tightly next to each other in the ID space - even if the database increment value is incorrect.&lt;br /&gt;
&lt;br /&gt;
Because of this methods simplcitity (&amp;quot;latest plus one&amp;quot;) - it is vulnerable to ID collisions if the object is actually created at a high frequency and the servers knowledge of the latest object is simply fractionally out of date.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=563</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=563"/>
		<updated>2023-08-30T19:04:22Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Low frequency block assignment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
As a result, alternate methods are also present for the purpose of solving these problems. Most sites however won&amp;#039;t actually encounter this as an issue.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;br /&gt;
&lt;br /&gt;
An example of an entity that uses low frequency block assignment is locales. Locales are edited rarely so they are cached meaning block assignment is active, but they also benefit greatly from having compact close together IDs due to the way how many SocialsStack features internally optimise localised sets. So, by setting it to low frequency block assignment the locales will end up tightly next to each other in the ID space - even if the database increment value is incorrect.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=562</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=562"/>
		<updated>2023-08-30T19:00:45Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Database assignment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
As a result, alternate methods are also present for the purpose of solving these problems. Most sites however won&amp;#039;t actually encounter this as an issue.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=561</id>
		<title>ContentSync</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=ContentSync&amp;diff=561"/>
		<updated>2023-08-30T19:00:01Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this f...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack sites are built for high throughput out of the box, however, there always comes a scale where you need to expand on to multiple servers. You need to measure this for your site, but that scale tends to be around 5000 simultaneous users. Beyond this value, NGINX and Linux start to struggle with file pointers and the database connection pool starts getting too busy. Usually the step from 1 to 2 servers requires large amounts of engineering to ensure everything works together correctly. SocialStack has built in clustering mechanics, ContentSync, which helps servers work together on most things automatically by default.&lt;br /&gt;
&lt;br /&gt;
= How ContentSync works =&lt;br /&gt;
&lt;br /&gt;
Servers in a SocialStack site cluster register themselves with an entry in your database, as an entity called ClusteredServer. This entity tracks the servers current public and private IP addresses to allow servers to communicate directly with each other via a mesh network, over a datacenter private LAN whenever possible. Depending on the host(s) you&amp;#039;re on, private VLANs can extend beyond a particular datacenter meaning the private IPs of your servers are reachable from any of your servers worldwide.&lt;br /&gt;
&lt;br /&gt;
Once a server is registered as a ClusteredServer and has started communicating with other servers, they talk about 2 main things: the status of network rooms and [[API Caching|cached entities]]. Network rooms provide an abstract mechanism for messaging a group of people (or a single person) who can be connected to any combination of servers, including one user connected to multiple servers. Cached entities, when edited on a server, triggers a message to all other present servers to tell them about the update. This allows cached objects to completely avoid hitting the database except for the server which triggered the update message in the first place.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
SocialStack has multiple methods for assigning an ID to a new object.&lt;br /&gt;
&lt;br /&gt;
== Database assignment ==&lt;br /&gt;
&lt;br /&gt;
The default for most entities is to use the method provided by the database: the latest database insertion ID. This has two problems:&lt;br /&gt;
&lt;br /&gt;
* Not all database engines support it. MySQL is just the default - SocialStack doesn&amp;#039;t actually rely on MySQL exclusively.&lt;br /&gt;
* It requires a centralised, singular counter. At high throughputs, keeping IDs from colliding becomes a bottleneck.&lt;br /&gt;
&lt;br /&gt;
== Block assignment ==&lt;br /&gt;
&lt;br /&gt;
The high throughput ID assignment mode, also activated when an entity is set to cache, is where each server in a cluster has exclusive ownership of a block of IDs. These blocks are spaced very far apart as they are formed by combining the ClusteredServer ID with the latest rolling number for that particular server.&lt;br /&gt;
&lt;br /&gt;
If you are seeing very large IDs appear in your database, it is because there are multiple entries in your ClusteredServer table (site_clusteredserver) and block assignment is active. If your site is not live yet, or there is only 1 server in your live cluster, you can truncate this table and restart the server to have it assign lower IDs if you would like it to do so.&lt;br /&gt;
&lt;br /&gt;
== Low frequency block assignment ==&lt;br /&gt;
&lt;br /&gt;
In this mode, clustered servers assume the object will be created at very low frequency and will self-assign IDs but will do it by discovering the latest assigned ID and adding 1. This always creates compact linear IDs and is similar to database assignment, except it does not rely on a central counter.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=560</id>
		<title>API Caching</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=560"/>
		<updated>2023-08-30T18:36:41Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Functionality */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;It&amp;#039;s often the case that you have at least a few [[entities]] which are accessed much more often than they are updated. Typically a site&amp;#039;s throughput is limited by the database so the less database traffic that happens the higher that throughput will be. SocialStack has a built in caching mechanism which allows you to mark an entity as cached. This means the list of all entities of that type is obtained once during server startup and then kept up to date through the [[ContentSync|internal server mesh network]].&lt;br /&gt;
&lt;br /&gt;
Caching therefore works best if you cache things accessed frequently but changed rarely. Some built in examples are site configuration, locales and the pages of your site. These are all cached by default.&lt;br /&gt;
&lt;br /&gt;
= Usage =&lt;br /&gt;
&lt;br /&gt;
To make an entity use the cache, simply call Cache(); from within the service constructor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public ThingService() {&lt;br /&gt;
&lt;br /&gt;
    // &amp;quot;Thing&amp;quot; entities will be cached.&lt;br /&gt;
    Cache();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it is a thirdparty service and you would like to avoid editing its source to allow for easier updates, you can also call Cache(); via the Events.Service.AfterStart event.&lt;br /&gt;
&lt;br /&gt;
= Functionality =&lt;br /&gt;
&lt;br /&gt;
The cache reuses objects whenever possible so when you ask a caching service to get or list objects then little to no allocations actually happen. These calls also return instantaneously as the object is coming from an already loaded in-memory data structure. In the example above, if you ask &amp;quot;ThingService&amp;quot; to get object with ID #14, that returned object is returned immediately, comes from the cache and is also reused.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Because the object is reused, avoid changing its fields anywhere other than via Update&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// private ThingService _thingService;&lt;br /&gt;
&lt;br /&gt;
// This returns instantly with a reused object.&lt;br /&gt;
var thing14 = await _thingService.Get(context, 14);&lt;br /&gt;
&lt;br /&gt;
// Because it&amp;#039;s reused, do not edit fields!&lt;br /&gt;
// thing14.Name = &amp;quot;do not do this&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// The only correct way to update fields is via the usual update call:&lt;br /&gt;
thing14 = await _thingService.Update(context, thing14, (Context c, Thing updateable) =&amp;gt; {&lt;br /&gt;
    updateable.Name = &amp;quot;Updated Name&amp;quot;;&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
&lt;br /&gt;
Because cached services are not depending on the database and do not allocate, they have extremely high throughput. You can use service Get hundreds of millions of times per second without risking long GC pauses (not that you would ever put this much traffic on one server though!). The JSON serialisation process also avoids as many allocations as possible and can also return more than 10 million results per second via a websocket. The limiting factor is usually the network bandwidth.&lt;br /&gt;
&lt;br /&gt;
Note that the use of the websocket is important if you are expecting this level of throughput being necessary. That is because the context needs to collect the user information which usually involves the database (the user service is rarely set to being cached, but you can do that too) and this context load only occurs once on a websocket.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
Because caching a service is primarily done for performance, enabling the cache also makes a service switch ID assignment model. This is covered in more detail on the [[ContentSync|clustering mechanics page]].&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=559</id>
		<title>API Caching</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=API_Caching&amp;diff=559"/>
		<updated>2023-08-30T18:35:36Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;It&amp;#039;s often the case that you have at least a few entities which are accessed much more often than they are updated. Typically a site&amp;#039;s throughput is limited by the databas...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;It&amp;#039;s often the case that you have at least a few [[entities]] which are accessed much more often than they are updated. Typically a site&amp;#039;s throughput is limited by the database so the less database traffic that happens the higher that throughput will be. SocialStack has a built in caching mechanism which allows you to mark an entity as cached. This means the list of all entities of that type is obtained once during server startup and then kept up to date through the [[ContentSync|internal server mesh network]].&lt;br /&gt;
&lt;br /&gt;
Caching therefore works best if you cache things accessed frequently but changed rarely. Some built in examples are site configuration, locales and the pages of your site. These are all cached by default.&lt;br /&gt;
&lt;br /&gt;
= Usage =&lt;br /&gt;
&lt;br /&gt;
To make an entity use the cache, simply call Cache(); from within the service constructor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public ThingService() {&lt;br /&gt;
&lt;br /&gt;
    // &amp;quot;Thing&amp;quot; entities will be cached.&lt;br /&gt;
    Cache();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it is a thirdparty service and you would like to avoid editing its source to allow for easier updates, you can also call Cache(); via the Events.Service.AfterStart event.&lt;br /&gt;
&lt;br /&gt;
= Functionality =&lt;br /&gt;
&lt;br /&gt;
The cache reuses objects whenever possible so when you ask a caching service to get or list objects then little to no allocations actually happen. These calls also return instantaneously as the object is coming from an already loaded in-memory data structure. In the example above, if you ask &amp;quot;ThingService&amp;quot; to get object with ID #14, that returned object is returned immediately, comes from the cache and is also reused.&lt;br /&gt;
&lt;br /&gt;
***Because the object is reused, avoid changing its fields anywhere other than via Update***.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// private ThingService _thingService;&lt;br /&gt;
&lt;br /&gt;
// This returns instantly with a reused object.&lt;br /&gt;
var thing14 = await _thingService.Get(context, 14);&lt;br /&gt;
&lt;br /&gt;
// Because it&amp;#039;s reused, do not edit fields!&lt;br /&gt;
// thing14.Name = &amp;quot;do not do this&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// The only correct way to update fields is via the usual update call:&lt;br /&gt;
thing14 = await _thingService.Update(context, thing14, (Context c, Thing updateable) =&amp;gt; {&lt;br /&gt;
    updateable.Name = &amp;quot;Updated Name&amp;quot;;&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
&lt;br /&gt;
Because cached services are not depending on the database and do not allocate, they have extremely high throughput. You can use service Get hundreds of millions of times per second without risking long GC pauses (not that you would ever put this much traffic on one server though!). The JSON serialisation process also avoids as many allocations as possible and can also return more than 10 million results per second via a websocket. The limiting factor is usually the network bandwidth.&lt;br /&gt;
&lt;br /&gt;
Note that the use of the websocket is important if you are expecting this level of throughput being necessary. That is because the context needs to collect the user information which usually involves the database (the user service is rarely set to being cached, but you can do that too) and this context load only occurs once on a websocket.&lt;br /&gt;
&lt;br /&gt;
= ID Assignment =&lt;br /&gt;
&lt;br /&gt;
Because caching a service is primarily done for performance, enabling the cache also makes a service switch ID assignment model. This is covered in more detail on the [[ContentSync|clustering mechanics page]].&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Using_npm_packages&amp;diff=558</id>
		<title>Using npm packages</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Using_npm_packages&amp;diff=558"/>
		<updated>2023-08-30T16:34:17Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;SocialStack puts performance first out of the box, with frontend javascript .js builds compiling near instantly on edits and being often less than 60kb when minified. SocialSt...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack puts performance first out of the box, with frontend javascript .js builds compiling near instantly on edits and being often less than 60kb when minified. SocialStack is also used to make native mobile app UIs which have a strong requirement of no remote dependencies and that is not a guarantee from npm packages. Many npm packages also only compile properly through webpack. This is often because the package specifically has configuration for webpack builds. As a result, webpack is largely unavoidable for building projects containing npm packages.&lt;br /&gt;
&lt;br /&gt;
Webpack builds are typically large and require usually very custom project specific configuration to tune it to the modules and build preferences you are using. For these reasons, SocialStack does not include webpack by default. To make use of npm modules, you&amp;#039;ll need to create an empty npm project with your prefered build chain in it, with only one small note: you&amp;#039;ll need to setup react (specifically it&amp;#039;s actually preact) as a remote alias. This is because preact is included directly inline in the HTML of the page rather than in any of the js builds such that preact can start warming the DOM whilst the rest of the page is still downloading.&lt;br /&gt;
&lt;br /&gt;
Tips:&lt;br /&gt;
&lt;br /&gt;
* Use a watcher based setup such that you can save files and then wait for your js to recompile - it&amp;#039;s a lot faster than it compiling from scratch.&lt;br /&gt;
&lt;br /&gt;
* Put your build output into the UI/Source directory. If it is small, it can just go directly in there and be bundled in with the rest of your UI. If it is large, you should put it in a folder called static. Contents of static directories are ignored by the built in js build chain but are still treated as part of the UI and copied automatically by the socialstack deployment helpers. You can then add its relative URL as a script tag in to the HtmlService site configuration. /pack/static/ is the effective location of the UI/Source directory. For example, UI/Source/Npm/static/hello.js is present at /pack/static/npm/static/hello.js.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Loop&amp;diff=557</id>
		<title>Loop</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Loop&amp;diff=557"/>
		<updated>2023-08-29T00:06:05Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Live loops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;Loop&amp;gt; is a very commonly used [https://reactjs.org/docs/react-component.html React component] installed by default in SocialStack projects. It is used to loop over lists of items typically from the API.&lt;br /&gt;
&lt;br /&gt;
=Sample Usage=&lt;br /&gt;
&lt;br /&gt;
Simple loop over everything of a given type (user in this case). Note that it will always be restricted by what you are permitted to see.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039;&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally you can filter your results based on some particular [[Filters|where filter]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;page&amp;#039; filter={&lt;br /&gt;
       {&lt;br /&gt;
              where: {&lt;br /&gt;
                     url: {&lt;br /&gt;
                            startsWith: &amp;#039;/en-admin/&amp;#039;&lt;br /&gt;
                     }&lt;br /&gt;
              }&lt;br /&gt;
       }&lt;br /&gt;
     }&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           page =&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{page.title}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Live loops=&lt;br /&gt;
&lt;br /&gt;
Sometimes you&amp;#039;ll want a loop to update in realtime - whenever someone else on the site sends you a message, for example. By default any loop can update live like this, and all it takes is the live prop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039; live&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Try creating a new user account - you&amp;#039;ll see it appear on this loop without needing to do anything else, again assuming that you are permitted to see the content.&lt;br /&gt;
&lt;br /&gt;
If you would like a callback whenever a new object is added via the live mechanism, you can use the onLiveCreate prop on your Loop.&lt;br /&gt;
&lt;br /&gt;
=Sorting=&lt;br /&gt;
&lt;br /&gt;
Filters support an order clause - see the [[Filters|page on filters]] for more information here.&lt;br /&gt;
&lt;br /&gt;
=Pagination=&lt;br /&gt;
&lt;br /&gt;
A loop can be automatically paginated using the UI/Paginator component. This is done using the paged prop:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039; paged={10}&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Paged itself has multiple configuration options, including the ability to specify which pagination component to use. At its basics though, just setting it to the number of items to display per page will get things moving.&lt;br /&gt;
&lt;br /&gt;
For advanced usage, the available configuration options are like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop paged={{&lt;br /&gt;
    pageSize: 10,&lt;br /&gt;
    showInput: false,&lt;br /&gt;
    showSummary: false,&lt;br /&gt;
    maxLinks: 3,&lt;br /&gt;
    module: &amp;#039;UI/MyCustomPaginator&amp;#039;&lt;br /&gt;
}} ...&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The module option should be used carefully to avoid inconsistency (i.e. a need to specify it on every loop would potentially arise). Instead, it&amp;#039;s recommended to uninstall the default UI/Paginator and create a custom component of the same name.&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like to restrict the result count but without a paginator visible, you can directly set pageSize and pageIndex into the filter. This is effectively what the paged prop does, along with displaying its additional UI.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop filter={{&lt;br /&gt;
    pageSize: 10,&lt;br /&gt;
    pageIndex: 0 // optional&lt;br /&gt;
}} ...&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Includes=&lt;br /&gt;
&lt;br /&gt;
[[Includes]] are a mechanism in SocialStack for obtaining related data in the same API call. They are a feature of the [[public API]] but are exposed via Loop too:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Looping over just included items =&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like to have live includes or loop over items which are &amp;quot;on&amp;quot; something else, then you can use the on filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;tag&amp;#039; filter={{on: {type: &amp;#039;Page&amp;#039;, id: 4}}}&amp;gt;&lt;br /&gt;
        {tag =&amp;gt; {&lt;br /&gt;
            return {tag.name};&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlike regular includes, these loops support going live too.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Presence&amp;diff=556</id>
		<title>Presence</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Presence&amp;diff=556"/>
		<updated>2023-08-16T16:44:25Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;It can often be useful to track online status of users on a website. By default, SocialStack sites use authenticated websockets which are typically the foundation of all presence checking as you can identify when they disconnect as well. There are some caveats to this which it helps to be aware of as it may affect your design goals and those are noted below too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Websocket based presence =&lt;br /&gt;
&lt;br /&gt;
The simplest way to handle user presence is by [[Events|handling]] the following events:&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.AfterLogin&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.AfterLogout&lt;br /&gt;
&lt;br /&gt;
These events are triggered whenever a user account has authenticated via the websocket. If you don&amp;#039;t care about user accounts and are more interested in the raw number of sessions, anonymous users included, use the underlying disconnect and connect events instead:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.Connected&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.Disconnected&lt;br /&gt;
&lt;br /&gt;
If a user is authenticated, it is often the case that Connected triggers immediately before AfterLogin does, and similarly, AfterLogout triggers immediately before Disconnected.&lt;br /&gt;
&lt;br /&gt;
= Active logins =&lt;br /&gt;
&lt;br /&gt;
There is nothing stopping someone opening multiple tabs of your website across multiple devices. Indeed this is very common with people frequently switching between mobile devices and desktops. A user is therefore not necessarily present or not - they can be present in 3 places, and all 3 sessions must end before their overall online state can change. If this sounds like a situation you need to handle, consider installing the ActiveLogins module which does it for you and keeps a user OnlineState field up to date.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/ActiveLogins&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates a personal &amp;quot;network room&amp;quot; which keeps track of a users presence from multiple devices, potentially also across multiple servers if you have a larger cluster too. The current state of the personal network room is tracked via &amp;quot;ActiveLoginHistory&amp;quot; records which can be used to identify a user logging in and out over time. These login audit trails are also often necessary on sites where unauthorised access is a concern due to the potential for privacy or property breaches.&lt;br /&gt;
&lt;br /&gt;
= Web presence caveats =&lt;br /&gt;
&lt;br /&gt;
Your website is likely to be left open in a tab for days or longer as well. In Chrome, an idle tab can actually keep the websocket connection open for a while (days) too. This means a user is &amp;quot;present&amp;quot; because the tab is open, but they are not active - often because the tab is one of dozens. If user activity is important to you, it is suggested to send a websocket message indicating activity and handle that directly. If you don&amp;#039;t receive such an activity message, then you can mark the websocket connection as &amp;quot;away&amp;quot;.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Presence&amp;diff=555</id>
		<title>Presence</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Presence&amp;diff=555"/>
		<updated>2023-08-16T16:34:59Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;It can often be useful to track online status of users on a website. By default, SocialStack sites use authenticated websockets which are typically the foundation of all prese...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;It can often be useful to track online status of users on a website. By default, SocialStack sites use authenticated websockets which are typically the foundation of all presense checking, as you can identify when they disconnect as well. There are some caveats to this which it helps to be aware of as it may affect your design goals and those are noted below too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Websocket based presence =&lt;br /&gt;
&lt;br /&gt;
The simplest way to handle user presence is by [[Events|handling]] the following events:&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.AfterLogin&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.AfterLogout&lt;br /&gt;
&lt;br /&gt;
These events are triggered whenever a user account has authenticated via the websocket. If you don&amp;#039;t care about user accounts and are more interested in the raw number of sessions, anonymous users included, use the underlying disconnect and connect events instead:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.Connected&lt;br /&gt;
&lt;br /&gt;
* Events.WebSocket.Disconnected&lt;br /&gt;
&lt;br /&gt;
If a user is authenticated, it is often the case that Connected triggers immediately before AfterLogin does, and similarly, AfterLogout triggers immediately before Disconnected.&lt;br /&gt;
&lt;br /&gt;
= Active logins =&lt;br /&gt;
&lt;br /&gt;
There is nothing stopping someone opening multiple tabs of your website across multiple devices. Indeed this is very common with people frequently switching between mobile devices and desktops. A user is therefore not necessarily present or not - they can be present in 3 places, and all 3 sessions must end before their overall online state can change. If this sounds like a situation you need to handle, consider installing the ActiveLogins module which does it for you and keeps a user OnlineState field up to date.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/ActiveLogins&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates a personal &amp;quot;network room&amp;quot; which keeps track of a users presence from multiple devices, potentially also across multiple servers if you have a larger cluster too. The current state of the personal network room is tracked via &amp;quot;ActiveLoginHistory&amp;quot; records which can be used to identify a user logging in and out over time. These login audit trails are also often necessary on sites where unauthorised access is a concern due to the potential for privacy or property breaches.&lt;br /&gt;
&lt;br /&gt;
= Web presence caveats =&lt;br /&gt;
&lt;br /&gt;
Your website is likely to be left open in a tab for days or longer as well. In Chrome, an idle tab can actually keep the websocket connection open for a while (days) too. This means a user is &amp;quot;present&amp;quot; because the tab is open, but they are not active - often because the tab is one of dozens. If user activity is important to you, it is suggested to send a websocket message indicating activity and handle that directly. If you don&amp;#039;t receive such an activity message, then you can mark the websocket connection as &amp;quot;away&amp;quot;.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=554</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=554"/>
		<updated>2023-08-03T19:01:14Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Getting one result */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one result ==&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like just one result row, use database.Select:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(UserCountResult), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT MyStoredFunction() as Count, Id from site_user where Id=@id&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result for user with ID 14:&lt;br /&gt;
    var userCounts = await database.Select&amp;lt;UserCountResult, uint&amp;gt;(context, query, typeof(UserCountResult), 14);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Select method supports one parameter called &amp;#039;&amp;#039;@id&amp;#039;&amp;#039; and its value is of the type you specify (uint in the example above). It is used with @id in your raw query and can also be ignored - you don&amp;#039;t have to use it if you don&amp;#039;t need it, in which case, just pass a 0 as its value.&lt;br /&gt;
&lt;br /&gt;
If you need additional parameters, it is suggested to use MySqlCommand directly instead. See below for more details on that.&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of multiple results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(UserCountResult), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using MySqlCommand directly ==&lt;br /&gt;
&lt;br /&gt;
The main thing you&amp;#039;ll want in order to use the underlying Oracle MySqlCommand object is a database connection instance. You can get this connection via MySQLDatabaseService&amp;#039;s GetConnection() method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using var connection = db.GetConnection();&lt;br /&gt;
await connection.OpenAsync();&lt;br /&gt;
var cmd = new MySqlCommand(&amp;quot;SELECT * ..&amp;quot;, connection);&lt;br /&gt;
&lt;br /&gt;
// cmd.Parameters.Add - see Oracle&amp;#039;s documentation for more on those.&lt;br /&gt;
&lt;br /&gt;
if(await cmd.ExecuteNonQueryAsync() &amp;gt; 0) {&lt;br /&gt;
    // Success!&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course gives you more options but you&amp;#039;ll need to map any results to an object yourself.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=553</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=553"/>
		<updated>2023-08-03T19:00:57Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Getting one or more results */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one result ==&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like just one result row, use database.Select:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT MyStoredFunction() as Count, Id from site_user where Id=@id&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result for user with ID 14:&lt;br /&gt;
    var userCounts = await database.Select&amp;lt;UserCountResult, uint&amp;gt;(context, query, typeof(UserCountResult), 14);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Select method supports one parameter called &amp;#039;&amp;#039;@id&amp;#039;&amp;#039; and its value is of the type you specify (uint in the example above). It is used with @id in your raw query and can also be ignored - you don&amp;#039;t have to use it if you don&amp;#039;t need it, in which case, just pass a 0 as its value.&lt;br /&gt;
&lt;br /&gt;
If you need additional parameters, it is suggested to use MySqlCommand directly instead. See below for more details on that.&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of multiple results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(UserCountResult), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using MySqlCommand directly ==&lt;br /&gt;
&lt;br /&gt;
The main thing you&amp;#039;ll want in order to use the underlying Oracle MySqlCommand object is a database connection instance. You can get this connection via MySQLDatabaseService&amp;#039;s GetConnection() method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using var connection = db.GetConnection();&lt;br /&gt;
await connection.OpenAsync();&lt;br /&gt;
var cmd = new MySqlCommand(&amp;quot;SELECT * ..&amp;quot;, connection);&lt;br /&gt;
&lt;br /&gt;
// cmd.Parameters.Add - see Oracle&amp;#039;s documentation for more on those.&lt;br /&gt;
&lt;br /&gt;
if(await cmd.ExecuteNonQueryAsync() &amp;gt; 0) {&lt;br /&gt;
    // Success!&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course gives you more options but you&amp;#039;ll need to map any results to an object yourself.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=552</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=552"/>
		<updated>2023-08-03T18:51:36Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Getting one result */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one result ==&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like just one result row, use database.Select:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT MyStoredFunction() as Count, Id from site_user where Id=@id&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result for user with ID 14:&lt;br /&gt;
    var userCounts = await database.Select&amp;lt;UserCountResult, uint&amp;gt;(context, query, typeof(UserCountResult), 14);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Select method supports one parameter called &amp;#039;&amp;#039;@id&amp;#039;&amp;#039; and its value is of the type you specify (uint in the example above). It is used with @id in your raw query and can also be ignored - you don&amp;#039;t have to use it if you don&amp;#039;t need it, in which case, just pass a 0 as its value.&lt;br /&gt;
&lt;br /&gt;
If you need additional parameters, it is suggested to use MySqlCommand directly instead. See below for more details on that.&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of multiple results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using MySqlCommand directly ==&lt;br /&gt;
&lt;br /&gt;
The main thing you&amp;#039;ll want in order to use the underlying Oracle MySqlCommand object is a database connection instance. You can get this connection via MySQLDatabaseService&amp;#039;s GetConnection() method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using var connection = db.GetConnection();&lt;br /&gt;
await connection.OpenAsync();&lt;br /&gt;
var cmd = new MySqlCommand(&amp;quot;SELECT * ..&amp;quot;, connection);&lt;br /&gt;
&lt;br /&gt;
// cmd.Parameters.Add - see Oracle&amp;#039;s documentation for more on those.&lt;br /&gt;
&lt;br /&gt;
if(await cmd.ExecuteNonQueryAsync() &amp;gt; 0) {&lt;br /&gt;
    // Success!&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course gives you more options but you&amp;#039;ll need to map any results to an object yourself.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=551</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=551"/>
		<updated>2023-08-03T18:51:14Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Getting one result */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one result ==&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like just one result row, use database.Select:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT MyStoredFunction() as Count, Id from site_user where Id=@id&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result for user with ID 14:&lt;br /&gt;
    var userCounts = await database.Select&amp;lt;UserCountResult, uint&amp;gt;(context, query, typeof(UserCountResult), 14);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Select method supports one parameter called &amp;#039;&amp;#039;@id&amp;#039;&amp;#039; and its value is of the type you specify (uint in the example above). It is used with @id in your raw query and can also be ignored - you don&amp;#039;t have to use it if you don&amp;#039;t need it, in which case, just pass a 0 as its value.&lt;br /&gt;
&lt;br /&gt;
If you need additional parameters, it is suggested to use MySqlCommand directly instead.&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of multiple results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using MySqlCommand directly ==&lt;br /&gt;
&lt;br /&gt;
The main thing you&amp;#039;ll want in order to use the underlying Oracle MySqlCommand object is a database connection instance. You can get this connection via MySQLDatabaseService&amp;#039;s GetConnection() method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using var connection = db.GetConnection();&lt;br /&gt;
await connection.OpenAsync();&lt;br /&gt;
var cmd = new MySqlCommand(&amp;quot;SELECT * ..&amp;quot;, connection);&lt;br /&gt;
&lt;br /&gt;
// cmd.Parameters.Add - see Oracle&amp;#039;s documentation for more on those.&lt;br /&gt;
&lt;br /&gt;
if(await cmd.ExecuteNonQueryAsync() &amp;gt; 0) {&lt;br /&gt;
    // Success!&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course gives you more options but you&amp;#039;ll need to map any results to an object yourself.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=550</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=550"/>
		<updated>2023-08-03T18:50:19Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one result ==&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like just one result row, use database.Select:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT MyStoredFunction() as Count, Id from site_user where Id=@id&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result for user with ID 14:&lt;br /&gt;
    var userCounts = await database.Select&amp;lt;UserCountResult, uint&amp;gt;(context, query, typeof(UserCountResult), 14);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Select method supports one parameter called ID and its value is of the type you specify (uint in the example above). It is used with @id in your raw query and can also be ignored - you don&amp;#039;t have to use it if you don&amp;#039;t need it, in which case, just pass a 0 as its value.&lt;br /&gt;
&lt;br /&gt;
If you need additional args, it is suggested to use MySqlCommand directly instead.&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of multiple results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using MySqlCommand directly ==&lt;br /&gt;
&lt;br /&gt;
The main thing you&amp;#039;ll want in order to use the underlying Oracle MySqlCommand object is a database connection instance. You can get this connection via MySQLDatabaseService&amp;#039;s GetConnection() method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using var connection = db.GetConnection();&lt;br /&gt;
await connection.OpenAsync();&lt;br /&gt;
var cmd = new MySqlCommand(&amp;quot;SELECT * ..&amp;quot;, connection);&lt;br /&gt;
&lt;br /&gt;
// cmd.Parameters.Add - see Oracle&amp;#039;s documentation for more on those.&lt;br /&gt;
&lt;br /&gt;
if(await cmd.ExecuteNonQueryAsync() &amp;gt; 0) {&lt;br /&gt;
    // Success!&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This of course gives you more options but you&amp;#039;ll need to map any results to an object yourself.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=549</id>
		<title>Raw Database Queries</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Raw_Database_Queries&amp;diff=549"/>
		<updated>2023-08-03T18:34:46Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack like many platforms suggests using its general purpose API&amp;#039;s instead of going under the hood to the database level to execute queries. This is because SocialStack isn&amp;#039;t strictly tied to any particular database engine (MySQL being just the default one) and as a result for code to be easier to share the general use API&amp;#039;s are always recommended. However though there are certainly times when you need an escape hatch and being able to just run a query can be useful.&lt;br /&gt;
&lt;br /&gt;
= MySQL Environment =&lt;br /&gt;
&lt;br /&gt;
SocialStack installs the [https://dev.mysql.com/doc/connector-net/en/connector-net-installation-binary-nuget.html Oracle MySQL C# library] via nuget which you can directly use to run MySqlCommand objects at the rawest level with its API. SocialStack also provides some abstractions to help catch common cases too which are available via the MySQLDatabaseService. You can access this service the same way as [[Services|any other service]] which is to say you can either use dependency injection or Services.Get like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Running a raw query =&lt;br /&gt;
&lt;br /&gt;
As SocialStack can&amp;#039;t guess what kind of result fields will be in your query results, there are a few different convenience methods for running queries on the database.&lt;br /&gt;
&lt;br /&gt;
== Executing a command with no results ==&lt;br /&gt;
&lt;br /&gt;
If you have no results and only want a true/ false if the query succeeded, use database.Run like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    var database = Services.Get&amp;lt;MySQLDatabaseService&amp;gt;();&lt;br /&gt;
    &lt;br /&gt;
    var succeeded = await database.Run(&amp;quot;UPDATE ...&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are sometimes used from [[Automations]] to do bulk database maintenance on a schedule.&lt;br /&gt;
&lt;br /&gt;
== Getting one or more results ==&lt;br /&gt;
&lt;br /&gt;
A list of one or more results can be obtained using database.List:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    using Api.Database;&lt;br /&gt;
   &lt;br /&gt;
    ...&lt;br /&gt;
    var query = Query.Select(typeof(User), &amp;quot;site_user&amp;quot;); // Give a hint of which main table you are using.&lt;br /&gt;
    query.SetRawQuery(&amp;quot;SELECT count(*) as Count, Id from site_user&amp;quot;); // Set the query.&lt;br /&gt;
    &lt;br /&gt;
    // Get the result(s):&lt;br /&gt;
    var userCounts = await database.List&amp;lt;UserCountResult&amp;gt;(context, query, typeof(UserCountResult));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Elsewhere you will need a class which describes the result row. In the example above, it is the class called UserCountResult, and it has at least 2 fields in the select statement so it would be at least like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
public class UserCountResult{&lt;br /&gt;
    public int Id;&lt;br /&gt;
    public int Count;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=548</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=548"/>
		<updated>2023-06-15T12:44:30Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Dynamic Includes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
A virtual field declared in this way will cause a convenient dropdown selector to appear for the ID field on the admin panel (UI/ContentSelect). Sometimes though your included type may have thousands or millions of rows, so a dropdown is not appropriate and instead a search first mechanism is required. You can turn on search mode like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[Data(&amp;quot;titleField&amp;quot;, &amp;quot;Name&amp;quot;)] /* The field name to use as the title in the results list (or in the dropdown) */&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;true&amp;quot;)] /* Use search mode instead of a dropdown of all */&lt;br /&gt;
public uint OtherTypeNameId;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Functional Includes =&lt;br /&gt;
&lt;br /&gt;
It can be useful to run some code on a particular object whilst its JSON is generating. Whilst you can do this with the List and Load event handlers, it is generally recommended to use a functional include instead as it has less overhead. Their downside is they are global: they operate on any type. If you would like the primary URL of a piece of content, it is available as a functional include called &amp;quot;primaryUrl&amp;quot;. In other words, including primaryUrl on a piece of content will provide a field called primaryUrl which would contain the page URL that content of that type is the primary content on.&lt;br /&gt;
&lt;br /&gt;
As with all includes, functional includes can also be nested. You can, for example include &amp;quot;tags,tags.primaryUrl&amp;quot; - to get a list of tags on this object, plus a primaryUrl for each tag - assuming some sort of /tags/{tag.slug} page exists.&lt;br /&gt;
&lt;br /&gt;
See also: PrimaryUrlValueGenerator for an example in your code.&lt;br /&gt;
&lt;br /&gt;
= Dynamic Includes =&lt;br /&gt;
&lt;br /&gt;
Dynamic includes are a mechanism where you can include something of a database specified type. This is useful in, for example, featured content scenario&amp;#039;s where the thing being featured could be a product, a business or really any other kind of content. In other words: the database is specifying what the content type is, and also the ID of the content too.&lt;br /&gt;
&lt;br /&gt;
To use dynamic includes, declare the field name containing the type as well as the field name containing the ID. Note that the content type field must be a string, and the ID field must be a ulong.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Including &amp;quot;item&amp;quot; when obtaining this featured content would also obtain the referenced type&lt;br /&gt;
// provided permission checks pass too of course.&lt;br /&gt;
[HasVirtualField(&amp;quot;Item&amp;quot;, &amp;quot;ContentType&amp;quot;, &amp;quot;ContentId&amp;quot;)]&lt;br /&gt;
public partial class FeaturedContent : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content type name, e.g. &amp;quot;Product&amp;quot;.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public string ContentType;&lt;br /&gt;
&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content ID. e.g. A product ID.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public ulong ContentId;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dynamic includes should however be considered a feature of last resort. If you know that something will be one of two types, then it is suggested to include lists of both of those types instead. As anything can be included this way, the UI is less structured as it doesn&amp;#039;t necessarily know for certain what it will have to display.&lt;br /&gt;
&lt;br /&gt;
Globally available includes can be nested on dynamic includes too. For example, &amp;quot;item,item.tags&amp;quot; will obtain the dynamic item plus any tags on it. As functional includes such as primaryUrl are global, they are are available as well. If you use a wildcard, it will use all available implicit global includes.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=547</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=547"/>
		<updated>2023-06-15T12:44:03Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Dynamic Includes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
A virtual field declared in this way will cause a convenient dropdown selector to appear for the ID field on the admin panel (UI/ContentSelect). Sometimes though your included type may have thousands or millions of rows, so a dropdown is not appropriate and instead a search first mechanism is required. You can turn on search mode like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[Data(&amp;quot;titleField&amp;quot;, &amp;quot;Name&amp;quot;)] /* The field name to use as the title in the results list (or in the dropdown) */&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;true&amp;quot;)] /* Use search mode instead of a dropdown of all */&lt;br /&gt;
public uint OtherTypeNameId;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Functional Includes =&lt;br /&gt;
&lt;br /&gt;
It can be useful to run some code on a particular object whilst its JSON is generating. Whilst you can do this with the List and Load event handlers, it is generally recommended to use a functional include instead as it has less overhead. Their downside is they are global: they operate on any type. If you would like the primary URL of a piece of content, it is available as a functional include called &amp;quot;primaryUrl&amp;quot;. In other words, including primaryUrl on a piece of content will provide a field called primaryUrl which would contain the page URL that content of that type is the primary content on.&lt;br /&gt;
&lt;br /&gt;
As with all includes, functional includes can also be nested. You can, for example include &amp;quot;tags,tags.primaryUrl&amp;quot; - to get a list of tags on this object, plus a primaryUrl for each tag - assuming some sort of /tags/{tag.slug} page exists.&lt;br /&gt;
&lt;br /&gt;
See also: PrimaryUrlValueGenerator for an example in your code.&lt;br /&gt;
&lt;br /&gt;
= Dynamic Includes =&lt;br /&gt;
&lt;br /&gt;
Dynamic includes are a mechanism where you can include something of a database specified type. This is useful in, for example, featured content scenario&amp;#039;s where the thing being featured could be a product, a business or really any other kind of content. In other words: the database is specifying what the content type is, and also the ID of the content too.&lt;br /&gt;
&lt;br /&gt;
To use dynamic includes, declare the field name containing the type as well as the field name containing the ID. Note that the content type field MUST be a string, and the ID field must be a ulong.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Including &amp;quot;item&amp;quot; when obtaining this featured content would also obtain the referenced type&lt;br /&gt;
// provided permission checks pass too of course.&lt;br /&gt;
[HasVirtualField(&amp;quot;Item&amp;quot;, &amp;quot;ContentType&amp;quot;, &amp;quot;ContentId&amp;quot;)]&lt;br /&gt;
public partial class FeaturedContent : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content type name, e.g. &amp;quot;Product&amp;quot;.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public string ContentType;&lt;br /&gt;
&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content ID. e.g. A product ID.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public ulong ContentId;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dynamic includes should however be considered a feature of last resort. If you know that something will be one of two types, then it is suggested to include lists of both of those types instead. As anything can be included this way, the UI is less structured as it doesn&amp;#039;t necessarily know for certain what it will have to display.&lt;br /&gt;
&lt;br /&gt;
Globally available includes can be nested on dynamic includes too. For example, &amp;quot;item,item.tags&amp;quot; will obtain the dynamic item plus any tags on it. As functional includes such as primaryUrl are global, they are are available as well. If you use a wildcard, it will use all available implicit global includes.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=546</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=546"/>
		<updated>2023-06-15T12:42:08Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Functional Includes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
A virtual field declared in this way will cause a convenient dropdown selector to appear for the ID field on the admin panel (UI/ContentSelect). Sometimes though your included type may have thousands or millions of rows, so a dropdown is not appropriate and instead a search first mechanism is required. You can turn on search mode like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[Data(&amp;quot;titleField&amp;quot;, &amp;quot;Name&amp;quot;)] /* The field name to use as the title in the results list (or in the dropdown) */&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;true&amp;quot;)] /* Use search mode instead of a dropdown of all */&lt;br /&gt;
public uint OtherTypeNameId;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Functional Includes =&lt;br /&gt;
&lt;br /&gt;
It can be useful to run some code on a particular object whilst its JSON is generating. Whilst you can do this with the List and Load event handlers, it is generally recommended to use a functional include instead as it has less overhead. Their downside is they are global: they operate on any type. If you would like the primary URL of a piece of content, it is available as a functional include called &amp;quot;primaryUrl&amp;quot;. In other words, including primaryUrl on a piece of content will provide a field called primaryUrl which would contain the page URL that content of that type is the primary content on.&lt;br /&gt;
&lt;br /&gt;
As with all includes, functional includes can also be nested. You can, for example include &amp;quot;tags,tags.primaryUrl&amp;quot; - to get a list of tags on this object, plus a primaryUrl for each tag - assuming some sort of /tags/{tag.slug} page exists.&lt;br /&gt;
&lt;br /&gt;
See also: PrimaryUrlValueGenerator for an example in your code.&lt;br /&gt;
&lt;br /&gt;
= Dynamic Includes =&lt;br /&gt;
&lt;br /&gt;
Dynamic includes are a mechanism where you can include something of a database specified type. This is useful in, for example, featured content scenario&amp;#039;s where the thing being featured could be a product, a business or really any other kind of content. In other words: the database is specifying what the content type is, and also the ID of the content too.&lt;br /&gt;
&lt;br /&gt;
To use dynamic includes, declare the field name containing the type as well as the field name containing the ID. Note that the content type field MUST be a string, and the ID field must be a ulong.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Including &amp;quot;item&amp;quot; when obtaining this featured content would also obtain the referenced type&lt;br /&gt;
// provided permission checks pass too of course.&lt;br /&gt;
[HasVirtualField(&amp;quot;Item&amp;quot;, &amp;quot;ContentType&amp;quot;, &amp;quot;ContentId&amp;quot;)]&lt;br /&gt;
public partial class FeaturedContent : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content type name, e.g. &amp;quot;Product&amp;quot;.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public string ContentType;&lt;br /&gt;
&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content ID. e.g. A product ID.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public ulong ContentId;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dynamic includes should however be considered a feature of last resort. As anything can be included this way, the UI is less structured as it doesn&amp;#039;t necessarily know for certain what it will have to display.&lt;br /&gt;
&lt;br /&gt;
Globally available includes can be nested on dynamic includes too. For example, &amp;quot;item,item.tags&amp;quot; will obtain the dynamic item plus any tags on it. As functional includes such as primaryUrl are global, they are are available as well. If you use a wildcard, it will use all available implicit global includes.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=545</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=545"/>
		<updated>2023-06-15T12:40:53Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
A virtual field declared in this way will cause a convenient dropdown selector to appear for the ID field on the admin panel (UI/ContentSelect). Sometimes though your included type may have thousands or millions of rows, so a dropdown is not appropriate and instead a search first mechanism is required. You can turn on search mode like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[Data(&amp;quot;titleField&amp;quot;, &amp;quot;Name&amp;quot;)] /* The field name to use as the title in the results list (or in the dropdown) */&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;true&amp;quot;)] /* Use search mode instead of a dropdown of all */&lt;br /&gt;
public uint OtherTypeNameId;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Functional Includes =&lt;br /&gt;
&lt;br /&gt;
It can be useful to run some code on a particular object whilst its JSON is generating. Whilst you can do this with the List and Load event handlers, it is generally recommended to use a functional include instead as it has less overhead. Their downside is they are global: they operate on any type. If you would like the primary URL of a piece of content, it is available as a functional include called &amp;quot;primaryUrl&amp;quot;. In other words, including primaryUrl on a piece of content will provide a field called primaryUrl which would contain the page URL that content of that type is the primary content on.&lt;br /&gt;
&lt;br /&gt;
See also: PrimaryUrlValueGenerator for an example in your code.&lt;br /&gt;
&lt;br /&gt;
= Dynamic Includes =&lt;br /&gt;
&lt;br /&gt;
Dynamic includes are a mechanism where you can include something of a database specified type. This is useful in, for example, featured content scenario&amp;#039;s where the thing being featured could be a product, a business or really any other kind of content. In other words: the database is specifying what the content type is, and also the ID of the content too.&lt;br /&gt;
&lt;br /&gt;
To use dynamic includes, declare the field name containing the type as well as the field name containing the ID. Note that the content type field MUST be a string, and the ID field must be a ulong.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Including &amp;quot;item&amp;quot; when obtaining this featured content would also obtain the referenced type&lt;br /&gt;
// provided permission checks pass too of course.&lt;br /&gt;
[HasVirtualField(&amp;quot;Item&amp;quot;, &amp;quot;ContentType&amp;quot;, &amp;quot;ContentId&amp;quot;)]&lt;br /&gt;
public partial class FeaturedContent : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content type name, e.g. &amp;quot;Product&amp;quot;.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public string ContentType;&lt;br /&gt;
&lt;br /&gt;
	/// &amp;lt;summary&amp;gt;&lt;br /&gt;
	/// The content ID. e.g. A product ID.&lt;br /&gt;
	/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
	public ulong ContentId;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dynamic includes should however be considered a feature of last resort. As anything can be included this way, the UI is less structured as it doesn&amp;#039;t necessarily know for certain what it will have to display.&lt;br /&gt;
&lt;br /&gt;
Globally available includes can be nested on dynamic includes too. For example, &amp;quot;item,item.tags&amp;quot; will obtain the dynamic item plus any tags on it. As functional includes such as primaryUrl are global, they are are available as well. If you use a wildcard, it will use all available implicit global includes.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Permissions&amp;diff=544</id>
		<title>Permissions</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Permissions&amp;diff=544"/>
		<updated>2023-06-11T16:02:00Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* How the permission system runs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack has a largely automated roles and capabilities permission system. In summary, a user has exactly one role, and a role is granted capabilities.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Core Roles =&lt;br /&gt;
&lt;br /&gt;
All SocialStack API&amp;#039;s have 6 core roles, in order of typical privilege:&lt;br /&gt;
&lt;br /&gt;
* Developer&lt;br /&gt;
  The ultimate super user, defaults to no restrictions on anything.&lt;br /&gt;
&lt;br /&gt;
* Admin&lt;br /&gt;
  An administrator which is typically locked from changing potentially site breaking configuration.&lt;br /&gt;
&lt;br /&gt;
* Member&lt;br /&gt;
  A fully setup logged in user on the site.&lt;br /&gt;
* Guest&lt;br /&gt;
  Typically this is someone who has created a user account but has not yet activated it in some way.&lt;br /&gt;
&lt;br /&gt;
* Public&lt;br /&gt;
  Anonymous users of the internet.&lt;br /&gt;
&lt;br /&gt;
* Banned&lt;br /&gt;
  A banned user account which typically gets bricked from most things.&lt;br /&gt;
&lt;br /&gt;
Via the admin panel, you can edit the name and grants of these roles. You can also create a custom role which derives from any of them (or derives from none of them if you want a clean slate).&lt;br /&gt;
&lt;br /&gt;
= Default Grants =&lt;br /&gt;
&lt;br /&gt;
The above core roles are important for thirdparty modules. Ideally you want to be able to install a module and by default its permissions are set well, regardless of what actual roles you have on your website. The pattern here is that a module can optionally define a set of default grants, usually in a file called Permissions.cs, and those grants target the above 6 core roles. Custom roles then inherit from a most appropriate core role in order for it to automatically also inherit general module default grants like this.&lt;br /&gt;
&lt;br /&gt;
If a module does not define a Permissions.cs, it will still gain basic best guess grants which are as follows:&lt;br /&gt;
&lt;br /&gt;
* Anyone can read it&lt;br /&gt;
* Only developer role can create new ones.&lt;br /&gt;
* You must be either a developer or the owner of a content (it&amp;#039;s UserId column equals your ID) to update or delete it.&lt;br /&gt;
&lt;br /&gt;
Importantly, all default grants can be overriden in the admin panel. If you set a grant rule on the admin panel, it will be the rule that wins always, meaning that the admin panels representation of the grants is always accurate.&lt;br /&gt;
&lt;br /&gt;
= Capabilities =&lt;br /&gt;
&lt;br /&gt;
Each role has a list of capabilities, which themselves are automatically generated based on what is in your project. In general they come in a group of 5:&lt;br /&gt;
&lt;br /&gt;
* _list&lt;br /&gt;
* _load&lt;br /&gt;
* _create&lt;br /&gt;
* _update&lt;br /&gt;
* _delete&lt;br /&gt;
&lt;br /&gt;
They aren&amp;#039;t restricted to this though - custom ones can also be defined. For a given capability in a given role, a grant rule is defined.&lt;br /&gt;
&lt;br /&gt;
= Grant Rules =&lt;br /&gt;
&lt;br /&gt;
Grant rules are the mechanism to indicate if a particular role is able to do something. They have 4 possible values:&lt;br /&gt;
&lt;br /&gt;
* Always deny (false)&lt;br /&gt;
  Anyone with this role will not be able to perform this capability.&lt;br /&gt;
&lt;br /&gt;
* Always allow (true)&lt;br /&gt;
  Anyone with this role will be able to perform this capability.&lt;br /&gt;
&lt;br /&gt;
* Inherit (null, the default)&lt;br /&gt;
  The grant is based on what the inherited role, if any, specifies. If the grant is not specified by any inherited role, then the grant is based on the default grants from the code.&lt;br /&gt;
&lt;br /&gt;
* Custom Rule (a string)&lt;br /&gt;
  This is a [[Filters|filter]] string, but one which supports constants. Typically you&amp;#039;d use filter functions in here.&lt;br /&gt;
&lt;br /&gt;
Whenever the API is considering returning a result, the grant rule is effectively AND&amp;#039;ed with any user provided filter.&lt;br /&gt;
&lt;br /&gt;
= How the permission system runs =&lt;br /&gt;
&lt;br /&gt;
The permission system adds itself as a Before* event handler with priority 1. For example, page_create is tested by some code in Events.Page.BeforeCreate.AddEventListener(..., 1). Because of the way how [[Events|the event system works]], this means all default event handlers run after the permission system does, but you can also run code just before it by using priority 0:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Events.Page.BeforeCreate.AddEventListener(async (Context context, Page page) =&amp;gt; {&lt;br /&gt;
    // Runs *before* the permission system&lt;br /&gt;
    return page;&lt;br /&gt;
}, 0); &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Permissions&amp;diff=543</id>
		<title>Permissions</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Permissions&amp;diff=543"/>
		<updated>2023-06-11T16:01:26Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack has a largely automated roles and capabilities permission system. In summary, a user has exactly one role, and a role is granted capabilities.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Core Roles =&lt;br /&gt;
&lt;br /&gt;
All SocialStack API&amp;#039;s have 6 core roles, in order of typical privilege:&lt;br /&gt;
&lt;br /&gt;
* Developer&lt;br /&gt;
  The ultimate super user, defaults to no restrictions on anything.&lt;br /&gt;
&lt;br /&gt;
* Admin&lt;br /&gt;
  An administrator which is typically locked from changing potentially site breaking configuration.&lt;br /&gt;
&lt;br /&gt;
* Member&lt;br /&gt;
  A fully setup logged in user on the site.&lt;br /&gt;
* Guest&lt;br /&gt;
  Typically this is someone who has created a user account but has not yet activated it in some way.&lt;br /&gt;
&lt;br /&gt;
* Public&lt;br /&gt;
  Anonymous users of the internet.&lt;br /&gt;
&lt;br /&gt;
* Banned&lt;br /&gt;
  A banned user account which typically gets bricked from most things.&lt;br /&gt;
&lt;br /&gt;
Via the admin panel, you can edit the name and grants of these roles. You can also create a custom role which derives from any of them (or derives from none of them if you want a clean slate).&lt;br /&gt;
&lt;br /&gt;
= Default Grants =&lt;br /&gt;
&lt;br /&gt;
The above core roles are important for thirdparty modules. Ideally you want to be able to install a module and by default its permissions are set well, regardless of what actual roles you have on your website. The pattern here is that a module can optionally define a set of default grants, usually in a file called Permissions.cs, and those grants target the above 6 core roles. Custom roles then inherit from a most appropriate core role in order for it to automatically also inherit general module default grants like this.&lt;br /&gt;
&lt;br /&gt;
If a module does not define a Permissions.cs, it will still gain basic best guess grants which are as follows:&lt;br /&gt;
&lt;br /&gt;
* Anyone can read it&lt;br /&gt;
* Only developer role can create new ones.&lt;br /&gt;
* You must be either a developer or the owner of a content (it&amp;#039;s UserId column equals your ID) to update or delete it.&lt;br /&gt;
&lt;br /&gt;
Importantly, all default grants can be overriden in the admin panel. If you set a grant rule on the admin panel, it will be the rule that wins always, meaning that the admin panels representation of the grants is always accurate.&lt;br /&gt;
&lt;br /&gt;
= Capabilities =&lt;br /&gt;
&lt;br /&gt;
Each role has a list of capabilities, which themselves are automatically generated based on what is in your project. In general they come in a group of 5:&lt;br /&gt;
&lt;br /&gt;
* _list&lt;br /&gt;
* _load&lt;br /&gt;
* _create&lt;br /&gt;
* _update&lt;br /&gt;
* _delete&lt;br /&gt;
&lt;br /&gt;
They aren&amp;#039;t restricted to this though - custom ones can also be defined. For a given capability in a given role, a grant rule is defined.&lt;br /&gt;
&lt;br /&gt;
= Grant Rules =&lt;br /&gt;
&lt;br /&gt;
Grant rules are the mechanism to indicate if a particular role is able to do something. They have 4 possible values:&lt;br /&gt;
&lt;br /&gt;
* Always deny (false)&lt;br /&gt;
  Anyone with this role will not be able to perform this capability.&lt;br /&gt;
&lt;br /&gt;
* Always allow (true)&lt;br /&gt;
  Anyone with this role will be able to perform this capability.&lt;br /&gt;
&lt;br /&gt;
* Inherit (null, the default)&lt;br /&gt;
  The grant is based on what the inherited role, if any, specifies. If the grant is not specified by any inherited role, then the grant is based on the default grants from the code.&lt;br /&gt;
&lt;br /&gt;
* Custom Rule (a string)&lt;br /&gt;
  This is a [[Filters|filter]] string, but one which supports constants. Typically you&amp;#039;d use filter functions in here.&lt;br /&gt;
&lt;br /&gt;
Whenever the API is considering returning a result, the grant rule is effectively AND&amp;#039;ed with any user provided filter.&lt;br /&gt;
&lt;br /&gt;
= How the permission system runs =&lt;br /&gt;
&lt;br /&gt;
The permission system adds itself as a Before* event handler with priority 1. For example, page_create is tested by some code in Events.Page.BeforeCreate.AddEventListener(..., 1). Because of the way how the event system works, this means all default event handlers run after the permission system does, but you can also run code just before it by using priority 0:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Events.Page.BeforeCreate.AddEventListener(async (Context context, Page page) =&amp;gt; {&lt;br /&gt;
    // Runs *before* the permission system&lt;br /&gt;
    return page;&lt;br /&gt;
}, 0); &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=541</id>
		<title>Search Component</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=541"/>
		<updated>2023-05-23T12:00:17Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;UI/Search is a simple component which displays a search bar. It can display the results or just provide the bar itself. It is widely used on the admin panel for searching content behind the scenes.&lt;br /&gt;
&lt;br /&gt;
= Render a search bar =&lt;br /&gt;
&lt;br /&gt;
A simple search bar which displays its own results is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search for=&amp;#039;page&amp;#039; field=&amp;#039;url&amp;#039; onFind={page =&amp;gt; { ..do something with page.. }}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will search the content type called &amp;quot;Page&amp;quot; by checking if the &amp;quot;Url&amp;quot; field contains the specified text, then list the results using the default rendering as a suggestions dropdown. When one of the suggestions is clicked on, onFind is triggered with the selected page.&lt;br /&gt;
&lt;br /&gt;
= Custom results handling =&lt;br /&gt;
&lt;br /&gt;
In many cases though you&amp;#039;ll just want to have the search bar just provide you with the inputted text - i.e. it just visually looks like a search bar and does nothing else. The search component provides a debounced onQuery prop which can be used to update other filters - particularly ones used by Loop. Because it is already debounced, you won&amp;#039;t need to worry about fast typers spamming a server - as would be the case if you directly used input onChange or keyup events.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search onSearch={text =&amp;gt; {&lt;br /&gt;
 .. do something with the searched for text here ..&lt;br /&gt;
}}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting text is then often well paired with &amp;lt;Loop&amp;gt; to create filtered results.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop over=&amp;#039;page&amp;#039; filter={{where: {&lt;br /&gt;
    url: {contains: theSearchText}&lt;br /&gt;
}}}&amp;gt;&lt;br /&gt;
{page =&amp;gt; {&lt;br /&gt;
&lt;br /&gt;
.. render page as you wish here..&lt;br /&gt;
&lt;br /&gt;
}}&amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=540</id>
		<title>Search Component</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=540"/>
		<updated>2023-05-23T11:59:54Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Custom results handling */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;UI/Search is a simple component which displays a search bar. It can display the results or just provide the bar itself. It is widely used on the admin panel for searching content behind the scenes.&lt;br /&gt;
&lt;br /&gt;
= Render a search bar =&lt;br /&gt;
&lt;br /&gt;
A simple search bar which displays its own results is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search for=&amp;#039;page&amp;#039; field=&amp;#039;url&amp;#039; onFind={page =&amp;gt; { ..do something with page.. }}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will search the content type called &amp;quot;Page&amp;quot; by checking if the &amp;quot;Url&amp;quot; field contains the specified text, then list the results using the default rendering as a suggestions dropdown. When one of the suggestions is clicked on, onFind is triggered with the selected page.&lt;br /&gt;
&lt;br /&gt;
= Custom results handling =&lt;br /&gt;
&lt;br /&gt;
In many cases though you&amp;#039;ll just want to have the search bar just provide you with the inputted text - i.e. it just visually looks like a search bar and does nothing else. The search component provides a debounced onQuery prop which can be used to update other filters - particularly ones used by Loop. Because it is already debounced, you won&amp;#039;t need to worry about fast typers spamming a server - as would be the case if you directly used input onChange or keyup events.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search onSearch={text =&amp;gt; {&lt;br /&gt;
 .. do something with the searched for text here ..&lt;br /&gt;
}}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting text is then often well paired with &amp;lt;Loop&amp;gt; to create filtered results.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop over=&amp;#039;page&amp;#039; filter={{where: {&lt;br /&gt;
    url: {contains: theSearchText}&lt;br /&gt;
}}}&amp;gt;&lt;br /&gt;
{page =&amp;gt; {&lt;br /&gt;
&lt;br /&gt;
.. render page as you wish here..&lt;br /&gt;
&lt;br /&gt;
}}&amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=539</id>
		<title>Search Component</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=539"/>
		<updated>2023-05-23T11:58:20Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Custom results handling */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;UI/Search is a simple component which displays a search bar. It can display the results or just provide the bar itself. It is widely used on the admin panel for searching content behind the scenes.&lt;br /&gt;
&lt;br /&gt;
= Render a search bar =&lt;br /&gt;
&lt;br /&gt;
A simple search bar which displays its own results is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search for=&amp;#039;page&amp;#039; field=&amp;#039;url&amp;#039; onFind={page =&amp;gt; { ..do something with page.. }}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will search the content type called &amp;quot;Page&amp;quot; by checking if the &amp;quot;Url&amp;quot; field contains the specified text, then list the results using the default rendering as a suggestions dropdown. When one of the suggestions is clicked on, onFind is triggered with the selected page.&lt;br /&gt;
&lt;br /&gt;
= Custom results handling =&lt;br /&gt;
&lt;br /&gt;
In many cases though you&amp;#039;ll just want to have the search bar just provide you with the inputted text - i.e. it just visually looks like a search bar and does nothing else. The search component provides a debounced onQuery prop which can be used to update other filters - particularly ones used by Loop. Because it is already debounced, you won&amp;#039;t need to worry about fast typers spamming a server - as would be the case if you directly used input onChange or keyup events.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search onSearch={text =&amp;gt; {&lt;br /&gt;
 .. do something with the searched for text here ..&lt;br /&gt;
}}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=538</id>
		<title>Search Component</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Component&amp;diff=538"/>
		<updated>2023-05-23T11:56:45Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;UI/Search is a simple component which displays a search bar. It can display the results or just provide the bar itself. It is widely used on the admin panel for searching cont...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;UI/Search is a simple component which displays a search bar. It can display the results or just provide the bar itself. It is widely used on the admin panel for searching content behind the scenes.&lt;br /&gt;
&lt;br /&gt;
= Render a search bar =&lt;br /&gt;
&lt;br /&gt;
A simple search bar which displays its own results is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search for=&amp;#039;page&amp;#039; field=&amp;#039;url&amp;#039; onFind={page =&amp;gt; { ..do something with page.. }}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will search the content type called &amp;quot;Page&amp;quot; by checking if the &amp;quot;Url&amp;quot; field contains the specified text, then list the results using the default rendering as a suggestions dropdown. When one of the suggestions is clicked on, onFind is triggered with the selected page.&lt;br /&gt;
&lt;br /&gt;
= Custom results handling =&lt;br /&gt;
&lt;br /&gt;
In many cases though you&amp;#039;ll just want to have the search bar just provide you with the inputted text - i.e. it just visually looks like a search bar and does nothing else. The search component provides a debounced onQuery prop which can be used to update other filters - particularly ones used by Loop. Because it is already debounced, you won&amp;#039;t need to worry about fast typers spamming a server - as would be the case if you directly used input onChange or keyup events.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
import Search from &amp;#039;UI/Search&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Search onQuery={text =&amp;gt; {&lt;br /&gt;
 .. do something with the searched for text here ..&lt;br /&gt;
}}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=User_Authentication&amp;diff=537</id>
		<title>User Authentication</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=User_Authentication&amp;diff=537"/>
		<updated>2023-05-05T18:33:14Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* How authentication works */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;User authentication is the process of users providing some sort of information and in return getting a token (usually stored as a cookie) which can then be used automatically on multiple requests. The flow is not specific to passwords and is also highly pluggable, with the ability to add multiple authentication requirements like two factor authentication (install &amp;#039;&amp;#039;&amp;#039;Api/TwoFactorGoogleAuth&amp;#039;&amp;#039;&amp;#039;) on the same user request, or use more advanced authentication mechanisms like SSO.&lt;br /&gt;
&lt;br /&gt;
= How authentication works =&lt;br /&gt;
&lt;br /&gt;
* First a request is sent to /v1/user/login. The JSON payload is mapped to a Api.Users.UserLogin object which is a partial class (extensible), meaning you can add custom fields to it if you need them.&lt;br /&gt;
&lt;br /&gt;
* Next, the UserLogin object is passed to an event handler called Events.UserOnAuthenticate&lt;br /&gt;
&lt;br /&gt;
* Multiple authentication providers can listen to this event. They can also work together, such as requiring both a password and a 2FA code. An authentication provider should attempt to identify the user account that it is authenticating, and then attempt to authenticate.&lt;br /&gt;
&lt;br /&gt;
* If you&amp;#039;re creating an authentication module which is intended to be used on multiple projects, it&amp;#039;s suggested to make sure that your module checks if some previous provider has already authenticated a user. Do this by checking if there is a LoginResult currently, and if there is, return it.&lt;br /&gt;
&lt;br /&gt;
* Two factor modules however require a LoginResult to exist. I.e. A previous authentication provider successfully authenticated, and now your secondary one must authenticate as well using other details from the UserLogin object. To signal that a successful authentication happened and that 2FA is required but the secondary auth has not passed yet, set MoreDetailRequired on the LoginResult object. This object is simply a canvas structured object, meaning it can indicate a UI component to display. See [https://github.com/socialstack-core/modules/blob/master/Api/TwoFactorGoogleAuth/TwoFactorGoogleAuthService.cs#L74 the Api/TwoFactorGoogleAuth] module for an example of that.&lt;br /&gt;
&lt;br /&gt;
* Projects default to having the password authentication module installed. You can uninstall it if you want, but its job is to provide username or email and password authentication behaviour. It&amp;#039;s &amp;#039;&amp;#039;&amp;#039;Api/PasswordAuth&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
* The PasswordAuth module selects a user to authenticate by checking if PasswordAuthService.OnSelectUser exists. This is a simple event method which you can set in order to change the actual query which runs when choosing a user to authenticate. Typically this event handler is set from within a small service such that it can have easy access to the UserService:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Users;&lt;br /&gt;
using Api.Eventing;&lt;br /&gt;
using Api.Contexts;&lt;br /&gt;
using Api.PasswordAuth;&lt;br /&gt;
&lt;br /&gt;
namespace Api.CustomUserQuery;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
public partial class CustomUserQueryService : AutoService&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    public CustomUserQueryService(PasswordAuthService passwordAuth)&lt;br /&gt;
    {&lt;br /&gt;
         passwordAuth.OnSelectUser = async (Context context, UserLogin loginDetails) =&amp;gt; {&lt;br /&gt;
            // Use any query you&amp;#039;d like along with custom fields in loginDetails to select the user account you would&lt;br /&gt;
            // like to use. Note that this does not handle authentication - only account identification.&lt;br /&gt;
            return await users.Where(&amp;quot;Email=?&amp;quot;, DataOptions.IgnorePermissions).Bind(loginDetails.Email).First(context);&lt;br /&gt;
         };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=536</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=536"/>
		<updated>2023-04-26T01:19:56Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Single objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
A virtual field declared in this way will cause a convenient dropdown selector to appear for the ID field on the admin panel (UI/ContentSelect). Sometimes though your included type may have thousands or millions of rows, so a dropdown is not appropriate and instead a search first mechanism is required. You can turn on search mode like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[Data(&amp;quot;titleField&amp;quot;, &amp;quot;Name&amp;quot;)] /* The field name to use as the title in the results list (or in the dropdown) */&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;true&amp;quot;)] /* Use search mode instead of a dropdown of all */&lt;br /&gt;
public uint OtherTypeNameId;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=535</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=535"/>
		<updated>2023-04-26T00:39:48Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Lists */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
        &lt;br /&gt;
        public string Name;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
=== On the admin panel ===&lt;br /&gt;
&lt;br /&gt;
By default a ListAs field will appear as a content selector on the admin panel. This content selector uses a searchbar to find the content you&amp;#039;re after and it will default to using the [[Content_Type_Meta_Fields|meta title field]] of the thing being marked with ListAs. Meta title fields take a guess at an appropriate field to use if they are not explicitly marked and it will ultimately default to searching by a field called &amp;quot;Name&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
For example, if you have the MyCustomType above, it has a field called Name so the search bar that appears will use that. If it did not have a field called Name, it will also attempt to use fields like &amp;quot;Title&amp;quot; and similar if they exist, based on the rules of the [[Content_Type_Meta_Fields|meta title field]]. If you want some other field to be searched by, mark it as the meta title field instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
    public string Registration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With that, the admin UI will then search by this field called Registration.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=534</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=534"/>
		<updated>2023-04-25T17:27:52Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Code varying by environment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Services class in the Api.Startup namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Startup;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Services.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
Services.Environment // The raw original name from appsettings&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On the frontend, Javascript and CSS can identify the current environment via the data-env-* attribute which is set on the &amp;lt;html&amp;gt; element:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html data-env=&amp;quot;dev&amp;quot; ..&lt;br /&gt;
&amp;lt;html data-env=&amp;quot;stage&amp;quot; ..&lt;br /&gt;
&amp;lt;html data-env=&amp;quot;prod&amp;quot; ..&lt;br /&gt;
&amp;lt;html data-env=&amp;quot;..custom..&amp;quot; ..&lt;br /&gt;
&lt;br /&gt;
The value used here is the normalised value, meaning if your appsettings originally specified &amp;quot;Production&amp;quot; then it would still be output as  data-env=&amp;quot;prod&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For the same reasons as described above, Javascript and CSS should also minimise their environment variance as much as possible and favour configuration settings instead of code.&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
If you are doing environment specific code, it is strongly advised to edit your appsettings.json to make your development instance think it is the environments you are affecting to test the code whilst it is being written.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;br /&gt;
&lt;br /&gt;
Such environment varying configuration can also be used by Javascript via useConfig from UI/Configuration and marking the config in the API with the [Frontend] attribute.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=533</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=533"/>
		<updated>2023-04-25T17:24:59Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Code varying by environment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Services class in the Api.Startup namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Startup;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Services.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
Services.Environment // The raw original name from appsettings&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
On the frontend, Javascript and CSS can identify the current environment via the data-env-* attribute which is set on the &amp;lt;html&amp;gt; element:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html data-env-dev ..&lt;br /&gt;
&amp;lt;html data-env-stage ..&lt;br /&gt;
&amp;lt;html data-env-prod ..&lt;br /&gt;
&amp;lt;html data-env=&amp;quot;custom&amp;quot; ..&lt;br /&gt;
&lt;br /&gt;
For the same reasons as described above, Javascript and CSS should also minimise their environment variance as much as possible and favour configuration settings instead of code.&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
If you are doing environment specific code, it is strongly advised to edit your appsettings.json to make your development instance think it is the environments you are affecting to test the code whilst it is being written.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;br /&gt;
&lt;br /&gt;
Such environment varying configuration can also be used by Javascript via useConfig from UI/Configuration and marking the config in the API with the [Frontend] attribute.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=532</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=532"/>
		<updated>2023-04-25T14:07:52Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Environment specific configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Services class in the Api.Startup namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Startup;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Services.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
Services.Environment // The raw original name from appsettings&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
If you are doing environment specific code, it is strongly advised to edit your appsettings.json to make your development instance think it is the environments you are affecting to test the code whilst it is being written.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;br /&gt;
&lt;br /&gt;
Such environment varying configuration can also be used by Javascript via useConfig from UI/Configuration and marking the config in the API with the [Frontend] attribute.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=531</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=531"/>
		<updated>2023-04-25T13:52:26Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Code varying by environment */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Services class in the Api.Startup namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Startup;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Services.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Services.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
Services.Environment // The raw original name from appsettings&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
If you are doing environment specific code, it is strongly advised to edit your appsettings.json to make your development instance think it is the environments you are affecting to test the code whilst it is being written.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=530</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=530"/>
		<updated>2023-04-25T13:44:02Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* How a server knows which environment it is */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Environment class in the Api.Configuration namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Configuration;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Environment.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Environment.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Environment.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
If you are doing environment specific code, it is strongly advised to edit your appsettings.json to make your development instance think it is the environments you are affecting to test the code whilst it is being written.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=529</id>
		<title>Environments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Environments&amp;diff=529"/>
		<updated>2023-04-25T13:41:23Z</updated>

		<summary type="html">&lt;p&gt;Luke: Created page with &amp;quot;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging envir...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Typically when a website is being created, it will go through 3 different &amp;quot;environments&amp;quot;. The development environment - literally on a developers computer - to a staging environment where the project owner can review changes, then finally on in to production. These 3 are commonly called dev, stage and prod in short form. Socialstack has some convenience mechanisms to allow configuration and code to target specific environments, with the caveat of some important best practises around this.&lt;br /&gt;
&lt;br /&gt;
= Code varying by environment =&lt;br /&gt;
&lt;br /&gt;
This is best avoided for a reason which will be touched on shortly. If your code must vary by environment, you can use the Environment class in the Api.Configuration namespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
using Api.Configuration;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Environment.IsProduction(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Environment.IsStaging(); // true/ false&lt;br /&gt;
&lt;br /&gt;
Environment.IsDevelopment(); // true/ false. This is almost never used and is instead more common to see #if DEBUG.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Best practises =&lt;br /&gt;
&lt;br /&gt;
Code which checks if it is running on stage or production has a nasty caveat: It probably isn&amp;#039;t going to be tested until it actually goes live. This can result in failed deployments and major downtime. If you do use environment specific code, make sure all code routes are at least tested beforehand. Instead though, favour environment specific configuration which is also easier to test before it goes live, as you can test it via quick configuration edits in the administration panel before it goes live too.&lt;br /&gt;
&lt;br /&gt;
= How a server knows which environment it is =&lt;br /&gt;
&lt;br /&gt;
A project&amp;#039;s appsettings.json must always define an Environment field. Typically the value in here is one of &amp;quot;dev&amp;quot;, &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot;. Deployed environments extend and override appsettings fields primarily with an appsettings.extension.json file. &lt;br /&gt;
&lt;br /&gt;
For example, a site deployed on SocialStack cloud will have its project appsettings.json as-is and then an appsettings.extension.json which declares Environment:stage or Environment:prod, which overrides whatever was in the original appsettings.json file. This extension file is also how environment specific database and metrics tracking configuration is passed in.&lt;br /&gt;
&lt;br /&gt;
= Environment specific configuration =&lt;br /&gt;
&lt;br /&gt;
Based on the best practises above, you&amp;#039;ll generally want to use environment specific configuration rather than code which varies based on the environment it detects. To do this, create multiple configuration entries in the configuration part of the admin panel with the same key. Configuration has a field called &amp;quot;Environments&amp;quot; which contains a comma separated list of the environments that this configuration entry applies to. Just like the Environment field, it is usually the environment shorthand name in lowercase.&lt;br /&gt;
&lt;br /&gt;
When a server loads configuration, it checks the environment field to see if it contains the environment name that this server is running as. This is a loose test meaning &amp;quot;production&amp;quot; will match &amp;quot;prod&amp;quot; and &amp;quot;staging&amp;quot; will match &amp;quot;stage&amp;quot; but it&amp;#039;s generally recommended to stick with the lowercase shorthand (dev, stage, prod) just in case custom code is explicitly checking this field for some unseen use cases.&lt;br /&gt;
&lt;br /&gt;
For example, a configuration entry with &amp;quot;Environments&amp;quot; set to &amp;quot;dev,stage&amp;quot; will be ignored by a production server. You should then have a separate configuration entry with the same key with &amp;quot;Environments&amp;quot; set to &amp;quot;prod&amp;quot; to configure production instances as well. This has the advantage of all servers being able to use the same configuration table making database dumps straight forward and more portable.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=511</id>
		<title>Editing Pages</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=511"/>
		<updated>2023-03-27T10:24:02Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Primary content type */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pages in SocialStack are a [[Entities|content type]] with a JSON structure that describes the [[Components|components]] on the page. We call this JSON structure [[Canvas|canvas JSON]]. A page can be both a singular URL, such as an about page on your website, or multiple, such as a page which displays blog posts.&lt;br /&gt;
&lt;br /&gt;
That&amp;#039;s quite a bit to unpack, so let&amp;#039;s get into the details.&lt;br /&gt;
&lt;br /&gt;
= What actually is a page anyway? =&lt;br /&gt;
&lt;br /&gt;
Pages started life like most other modules in SocialStack - via the [[Generate|generate command]]. This means it fundamentally is no different from other modules, aside from it is simply included in the list of default modules as &amp;quot;Api/Pages&amp;quot;. You can actually uninstall this module and you&amp;#039;ll end up with an API only headless instance. This also means that they use an automated database structure as well as a service.&lt;br /&gt;
&lt;br /&gt;
Pages have a database stored field called BodyJson which is a textual value that contains JSON. [[Canvas|Canvas JSON]] is a very light representation of a tree of components which, when rendered by the Canvas component on the frontend, results in your page on the screen.&lt;br /&gt;
&lt;br /&gt;
Pages also have a URL field which indicates simply which URL(s) this page will be served for. For example, a page with a url of &amp;#039;&amp;#039;&amp;#039;/about&amp;#039;&amp;#039;&amp;#039; will be served on example.com/about. URLs can also support tokens such that one page can be used to display multiple dynamic objects - a blog or forum for example. See the section about tokens below for more information on this.&lt;br /&gt;
&lt;br /&gt;
Canvas JSON is not specific to pages - it is used by emails too for example. The canvas JSON editor can be used by any content type by creating a string field which ends with &amp;quot;Json&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= How the editor works =&lt;br /&gt;
&lt;br /&gt;
The [[Canvas Editor|canvas editor]] itself is designed to be a what-you-see-is-what-you-get experience, but there are some things to be aware of.&lt;br /&gt;
&lt;br /&gt;
Firstly, it has an add component window which lists available components on the site. This window will only display components which have React propTypes set. It can be an empty object; it just needs to be not null. When generating a new UI component with the [[Generate|generate command]], it will also include a commented out propTypes too.&lt;br /&gt;
&lt;br /&gt;
Secondly, the propTypes are directly used to establish what options a component has. For example if you drop an image onto the canvas, the UI/Image component has propTypes which include which actual file to display. A generated component also has some examples of these too.&lt;br /&gt;
&lt;br /&gt;
Finally, the editor does also have a source view which lets you see and edit the underlying canvas JSON instead. This can often be useful for quickly fixing a layout which is otherwise awkward or ambiguous in the visual view. Check out the guide on [[Canvas|canvas JSON]] for more details on valid content here.&lt;br /&gt;
&lt;br /&gt;
= URL Tokens =&lt;br /&gt;
&lt;br /&gt;
You may have a page which displays, for example, blog posts. This is achieved by using tokens in your URL, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The value of a token is usually {contentType.fieldName} meaning tokens will work for most content including custom types. It doesn&amp;#039;t have to be a content type though - you can also use arbritrary token values, like this:&lt;br /&gt;
&lt;br /&gt;
/password-reset/{token}/&lt;br /&gt;
&lt;br /&gt;
The value of this token is accessible in the same way (see the section below).&lt;br /&gt;
&lt;br /&gt;
You can use multiple tokens in a single URL too, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blog.slug}/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
== Accessing the values ==&lt;br /&gt;
&lt;br /&gt;
On the frontend the token values are available via the page router, which can be used via a React hook or consumer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import {useRouter} from &amp;#039;UI/Session&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var {pageState} = useRouter();&lt;br /&gt;
&lt;br /&gt;
// pageState.tokens, pageState.tokenValues&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The tokens is an array of each token discovered from the URL.&lt;br /&gt;
&lt;br /&gt;
Alternatively a [[Tokens|token resolver]] can be used which allows a CMS/editor friendly textual representation of the tokens as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import {useTokens} from &amp;#039;UI/Token&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Assuming the original url was /blogs/{blogpost.slug}&lt;br /&gt;
// This would typically be a prop so someone can change the text with the editor&lt;br /&gt;
var inputText = &amp;#039;The blog slug in the url is ${url.blogpost.slug}&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
// Swaps the ${tokens} with their underlying value.&lt;br /&gt;
var processedText = useTokens(inputText);&lt;br /&gt;
&lt;br /&gt;
return &amp;lt;h1&amp;gt;{processedText}&amp;lt;/h1&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The token resolver works with either {typename.fieldName} or {typelessTokens} in the URL as well.&lt;br /&gt;
&lt;br /&gt;
== Primary content type ==&lt;br /&gt;
&lt;br /&gt;
If you use at least one content type token (such as {blogpost.slug}) in your URL, the &amp;#039;&amp;#039;&amp;#039;very last&amp;#039;&amp;#039;&amp;#039; of these tokens in the URL will be resolved to the content object it refers to, and this object is then called the primary content of the page.&lt;br /&gt;
&lt;br /&gt;
For example going back to blog posts:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;primary content&amp;quot; of the page at /blog/hello-world/ will be a BlogPost with the field called &amp;quot;Slug&amp;quot; equal to the value &amp;quot;hello-world&amp;quot;. The primary content on the page has some special use as it can be used in the page title, description etc amongst other things.&lt;br /&gt;
&lt;br /&gt;
For convenience in React, you can quickly access the current primary content on a page like this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Content primary&amp;gt;&lt;br /&gt;
{primaryObject =&amp;gt; {&lt;br /&gt;
  if(!primaryObject) {&lt;br /&gt;
    return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  // primaryObject is e.g. a BlogPost, depending on which page we&amp;#039;re on&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you need the URL for a piece of content, it must be the primary object on at least 1 page. So long as such as page exists, PageService.GetUrl will return the backwards resolved URL for you. Note that because the rule about the &amp;quot;very last&amp;quot; token above is important as it means you can have multiple pages with the same primary content. A situation where this happens might be, for example:&lt;br /&gt;
&lt;br /&gt;
* /blog/hello-world/&lt;br /&gt;
&lt;br /&gt;
* /blog/hello-world/comments&lt;br /&gt;
&lt;br /&gt;
And similar &amp;quot;sub pages&amp;quot; for a piece of content. Because of this, the shortest one wins as being the one which will be used for the contents URL. I.e. if you ask for the URL for the blog with the slug &amp;quot;hello-world&amp;quot;, then its URL would be &amp;quot;/blog/hello-world/&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Page title =&lt;br /&gt;
&lt;br /&gt;
The title field of a page is used for setting the title bar of the page and will also appear on e.g. search results. If your page has primary content on it (see above), you can use tokens in the page title as well. Again continuing with the BlogPost example, that may be simply the title of the post:&lt;br /&gt;
&lt;br /&gt;
My Great Site | {blogpost.title}&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=510</id>
		<title>Editing Pages</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=510"/>
		<updated>2023-03-27T10:23:20Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Primary content type */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pages in SocialStack are a [[Entities|content type]] with a JSON structure that describes the [[Components|components]] on the page. We call this JSON structure [[Canvas|canvas JSON]]. A page can be both a singular URL, such as an about page on your website, or multiple, such as a page which displays blog posts.&lt;br /&gt;
&lt;br /&gt;
That&amp;#039;s quite a bit to unpack, so let&amp;#039;s get into the details.&lt;br /&gt;
&lt;br /&gt;
= What actually is a page anyway? =&lt;br /&gt;
&lt;br /&gt;
Pages started life like most other modules in SocialStack - via the [[Generate|generate command]]. This means it fundamentally is no different from other modules, aside from it is simply included in the list of default modules as &amp;quot;Api/Pages&amp;quot;. You can actually uninstall this module and you&amp;#039;ll end up with an API only headless instance. This also means that they use an automated database structure as well as a service.&lt;br /&gt;
&lt;br /&gt;
Pages have a database stored field called BodyJson which is a textual value that contains JSON. [[Canvas|Canvas JSON]] is a very light representation of a tree of components which, when rendered by the Canvas component on the frontend, results in your page on the screen.&lt;br /&gt;
&lt;br /&gt;
Pages also have a URL field which indicates simply which URL(s) this page will be served for. For example, a page with a url of &amp;#039;&amp;#039;&amp;#039;/about&amp;#039;&amp;#039;&amp;#039; will be served on example.com/about. URLs can also support tokens such that one page can be used to display multiple dynamic objects - a blog or forum for example. See the section about tokens below for more information on this.&lt;br /&gt;
&lt;br /&gt;
Canvas JSON is not specific to pages - it is used by emails too for example. The canvas JSON editor can be used by any content type by creating a string field which ends with &amp;quot;Json&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= How the editor works =&lt;br /&gt;
&lt;br /&gt;
The [[Canvas Editor|canvas editor]] itself is designed to be a what-you-see-is-what-you-get experience, but there are some things to be aware of.&lt;br /&gt;
&lt;br /&gt;
Firstly, it has an add component window which lists available components on the site. This window will only display components which have React propTypes set. It can be an empty object; it just needs to be not null. When generating a new UI component with the [[Generate|generate command]], it will also include a commented out propTypes too.&lt;br /&gt;
&lt;br /&gt;
Secondly, the propTypes are directly used to establish what options a component has. For example if you drop an image onto the canvas, the UI/Image component has propTypes which include which actual file to display. A generated component also has some examples of these too.&lt;br /&gt;
&lt;br /&gt;
Finally, the editor does also have a source view which lets you see and edit the underlying canvas JSON instead. This can often be useful for quickly fixing a layout which is otherwise awkward or ambiguous in the visual view. Check out the guide on [[Canvas|canvas JSON]] for more details on valid content here.&lt;br /&gt;
&lt;br /&gt;
= URL Tokens =&lt;br /&gt;
&lt;br /&gt;
You may have a page which displays, for example, blog posts. This is achieved by using tokens in your URL, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The value of a token is usually {contentType.fieldName} meaning tokens will work for most content including custom types. It doesn&amp;#039;t have to be a content type though - you can also use arbritrary token values, like this:&lt;br /&gt;
&lt;br /&gt;
/password-reset/{token}/&lt;br /&gt;
&lt;br /&gt;
The value of this token is accessible in the same way (see the section below).&lt;br /&gt;
&lt;br /&gt;
You can use multiple tokens in a single URL too, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blog.slug}/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
== Accessing the values ==&lt;br /&gt;
&lt;br /&gt;
On the frontend the token values are available via the page router, which can be used via a React hook or consumer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import {useRouter} from &amp;#039;UI/Session&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var {pageState} = useRouter();&lt;br /&gt;
&lt;br /&gt;
// pageState.tokens, pageState.tokenValues&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The tokens is an array of each token discovered from the URL.&lt;br /&gt;
&lt;br /&gt;
Alternatively a [[Tokens|token resolver]] can be used which allows a CMS/editor friendly textual representation of the tokens as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import {useTokens} from &amp;#039;UI/Token&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Assuming the original url was /blogs/{blogpost.slug}&lt;br /&gt;
// This would typically be a prop so someone can change the text with the editor&lt;br /&gt;
var inputText = &amp;#039;The blog slug in the url is ${url.blogpost.slug}&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
// Swaps the ${tokens} with their underlying value.&lt;br /&gt;
var processedText = useTokens(inputText);&lt;br /&gt;
&lt;br /&gt;
return &amp;lt;h1&amp;gt;{processedText}&amp;lt;/h1&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The token resolver works with either {typename.fieldName} or {typelessTokens} in the URL as well.&lt;br /&gt;
&lt;br /&gt;
== Primary content type ==&lt;br /&gt;
&lt;br /&gt;
If you use at least one content type token (such as {blogpost.slug}) in your URL, the &amp;#039;&amp;#039;&amp;#039;very last&amp;#039;&amp;#039;&amp;#039; of these tokens in the URL will be resolved to the content object it refers to, and this object is then called the primary content of the page.&lt;br /&gt;
&lt;br /&gt;
For example going back to blog posts:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;primary content&amp;quot; of the page at /blog/hello-world/ will be a BlogPost with the field called &amp;quot;Slug&amp;quot; equal to the value &amp;quot;hello-world&amp;quot;. The primary content on the page has some special use as it can be used in the page title, description etc amongst other things.&lt;br /&gt;
&lt;br /&gt;
For convenience in React, you can quickly access the current primary content on a page like this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Content primary&amp;gt;&lt;br /&gt;
{primaryObject =&amp;gt; {&lt;br /&gt;
  if(!primaryObject) {&lt;br /&gt;
    return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  // primaryObject is e.g. a BlogPost, depending on which page we&amp;#039;re on&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you need the URL for a piece of content, it must be the primary object on at least 1 page. So long as such as page exists, PageService.GetUrl will return the backwards resolved URL for you.&lt;br /&gt;
&lt;br /&gt;
Note that because the rule about the &amp;quot;very last&amp;quot; token above is important as it means you can have multiple pages with the same primary content. A situation where this happens might be, for example:&lt;br /&gt;
&lt;br /&gt;
* /blog/hello-world/&lt;br /&gt;
&lt;br /&gt;
* /blog/hello-world/comments&lt;br /&gt;
&lt;br /&gt;
And similar &amp;quot;sub pages&amp;quot; for a piece of content. Because of this, the shortest one wins as being the one which will be used for the contents URL. I.e. if you ask for the URL for the blog with the slug &amp;quot;hello-world&amp;quot;, then its URL would be &amp;quot;/blog/hello-world/&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Page title =&lt;br /&gt;
&lt;br /&gt;
The title field of a page is used for setting the title bar of the page and will also appear on e.g. search results. If your page has primary content on it (see above), you can use tokens in the page title as well. Again continuing with the BlogPost example, that may be simply the title of the post:&lt;br /&gt;
&lt;br /&gt;
My Great Site | {blogpost.title}&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=509</id>
		<title>Editing Pages</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Editing_Pages&amp;diff=509"/>
		<updated>2023-03-27T10:23:07Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Primary content type */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pages in SocialStack are a [[Entities|content type]] with a JSON structure that describes the [[Components|components]] on the page. We call this JSON structure [[Canvas|canvas JSON]]. A page can be both a singular URL, such as an about page on your website, or multiple, such as a page which displays blog posts.&lt;br /&gt;
&lt;br /&gt;
That&amp;#039;s quite a bit to unpack, so let&amp;#039;s get into the details.&lt;br /&gt;
&lt;br /&gt;
= What actually is a page anyway? =&lt;br /&gt;
&lt;br /&gt;
Pages started life like most other modules in SocialStack - via the [[Generate|generate command]]. This means it fundamentally is no different from other modules, aside from it is simply included in the list of default modules as &amp;quot;Api/Pages&amp;quot;. You can actually uninstall this module and you&amp;#039;ll end up with an API only headless instance. This also means that they use an automated database structure as well as a service.&lt;br /&gt;
&lt;br /&gt;
Pages have a database stored field called BodyJson which is a textual value that contains JSON. [[Canvas|Canvas JSON]] is a very light representation of a tree of components which, when rendered by the Canvas component on the frontend, results in your page on the screen.&lt;br /&gt;
&lt;br /&gt;
Pages also have a URL field which indicates simply which URL(s) this page will be served for. For example, a page with a url of &amp;#039;&amp;#039;&amp;#039;/about&amp;#039;&amp;#039;&amp;#039; will be served on example.com/about. URLs can also support tokens such that one page can be used to display multiple dynamic objects - a blog or forum for example. See the section about tokens below for more information on this.&lt;br /&gt;
&lt;br /&gt;
Canvas JSON is not specific to pages - it is used by emails too for example. The canvas JSON editor can be used by any content type by creating a string field which ends with &amp;quot;Json&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= How the editor works =&lt;br /&gt;
&lt;br /&gt;
The [[Canvas Editor|canvas editor]] itself is designed to be a what-you-see-is-what-you-get experience, but there are some things to be aware of.&lt;br /&gt;
&lt;br /&gt;
Firstly, it has an add component window which lists available components on the site. This window will only display components which have React propTypes set. It can be an empty object; it just needs to be not null. When generating a new UI component with the [[Generate|generate command]], it will also include a commented out propTypes too.&lt;br /&gt;
&lt;br /&gt;
Secondly, the propTypes are directly used to establish what options a component has. For example if you drop an image onto the canvas, the UI/Image component has propTypes which include which actual file to display. A generated component also has some examples of these too.&lt;br /&gt;
&lt;br /&gt;
Finally, the editor does also have a source view which lets you see and edit the underlying canvas JSON instead. This can often be useful for quickly fixing a layout which is otherwise awkward or ambiguous in the visual view. Check out the guide on [[Canvas|canvas JSON]] for more details on valid content here.&lt;br /&gt;
&lt;br /&gt;
= URL Tokens =&lt;br /&gt;
&lt;br /&gt;
You may have a page which displays, for example, blog posts. This is achieved by using tokens in your URL, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The value of a token is usually {contentType.fieldName} meaning tokens will work for most content including custom types. It doesn&amp;#039;t have to be a content type though - you can also use arbritrary token values, like this:&lt;br /&gt;
&lt;br /&gt;
/password-reset/{token}/&lt;br /&gt;
&lt;br /&gt;
The value of this token is accessible in the same way (see the section below).&lt;br /&gt;
&lt;br /&gt;
You can use multiple tokens in a single URL too, like this:&lt;br /&gt;
&lt;br /&gt;
/blog/{blog.slug}/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
== Accessing the values ==&lt;br /&gt;
&lt;br /&gt;
On the frontend the token values are available via the page router, which can be used via a React hook or consumer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import {useRouter} from &amp;#039;UI/Session&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var {pageState} = useRouter();&lt;br /&gt;
&lt;br /&gt;
// pageState.tokens, pageState.tokenValues&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The tokens is an array of each token discovered from the URL.&lt;br /&gt;
&lt;br /&gt;
Alternatively a [[Tokens|token resolver]] can be used which allows a CMS/editor friendly textual representation of the tokens as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import {useTokens} from &amp;#039;UI/Token&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Assuming the original url was /blogs/{blogpost.slug}&lt;br /&gt;
// This would typically be a prop so someone can change the text with the editor&lt;br /&gt;
var inputText = &amp;#039;The blog slug in the url is ${url.blogpost.slug}&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
// Swaps the ${tokens} with their underlying value.&lt;br /&gt;
var processedText = useTokens(inputText);&lt;br /&gt;
&lt;br /&gt;
return &amp;lt;h1&amp;gt;{processedText}&amp;lt;/h1&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The token resolver works with either {typename.fieldName} or {typelessTokens} in the URL as well.&lt;br /&gt;
&lt;br /&gt;
== Primary content type ==&lt;br /&gt;
&lt;br /&gt;
If you use at least one content type token (such as {blogpost.slug}) in your URL, the &amp;#039;&amp;#039;&amp;#039;very last&amp;#039;&amp;#039;&amp;#039; of these tokens in the URL will be resolved to the content object it refers to, and this object is then called the primary content of the page.&lt;br /&gt;
&lt;br /&gt;
For example going back to blog posts:&lt;br /&gt;
&lt;br /&gt;
/blog/{blogpost.slug}/&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;primary content&amp;quot; of the page at /blog/hello-world/ will be a BlogPost with the field called &amp;quot;Slug&amp;quot; equal to the value &amp;quot;hello-world&amp;quot;. The primary content on the page has some special use as it can be used in the page title, description etc amongst other things.&lt;br /&gt;
&lt;br /&gt;
For convenience in React, you can quickly access the current primary content on a page like this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Content primary&amp;gt;&lt;br /&gt;
{primaryObject =&amp;gt; {&lt;br /&gt;
  if(!primaryObject) {&lt;br /&gt;
    return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
  }&lt;br /&gt;
  &lt;br /&gt;
  // primaryObject is e.g. a BlogPost, depending on which page we&amp;#039;re on&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you need the URL for a piece of content, it must be the primary object on at least 1 page. So long as such as page exists, PageService.GetUrl will return the backwards resolved URL for you.&lt;br /&gt;
&lt;br /&gt;
Note that because the rule about the &amp;quot;very last&amp;quot; token above is important as it means you can have multiple pages with the same primary content. A situation where this happens might be, for example:&lt;br /&gt;
&lt;br /&gt;
/blog/hello-world/&lt;br /&gt;
/blog/hello-world/comments&lt;br /&gt;
&lt;br /&gt;
And similar &amp;quot;sub pages&amp;quot; for a piece of content. Because of this, the shortest one wins as being the one which will be used for the contents URL. I.e. if you ask for the URL for the blog with the slug &amp;quot;hello-world&amp;quot;, then its URL would be &amp;quot;/blog/hello-world/&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Page title =&lt;br /&gt;
&lt;br /&gt;
The title field of a page is used for setting the title bar of the page and will also appear on e.g. search results. If your page has primary content on it (see above), you can use tokens in the page title as well. Again continuing with the BlogPost example, that may be simply the title of the post:&lt;br /&gt;
&lt;br /&gt;
My Great Site | {blogpost.title}&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=497</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=497"/>
		<updated>2023-03-16T17:22:38Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Frontend Usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[forms|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=496</id>
		<title>Includes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Includes&amp;diff=496"/>
		<updated>2023-03-16T17:22:19Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Frontend Usage */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Includes are a mechanism in SocialStack which allow you to request additional content when obtaining an entity (or a list of entities) from the API. For example, you have a loop of things and would like to also display the name of the user who created each one. Includes are not case sensitive.&lt;br /&gt;
&lt;br /&gt;
=Frontend Usage=&lt;br /&gt;
&lt;br /&gt;
Typically includes are used from either Loop or Content:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page/list&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import Content from &amp;#039;UI/Content&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Content type=&amp;#039;page&amp;#039; id={1} includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            if(!page){&lt;br /&gt;
                // Just an example. The page might also not exist. See &amp;lt;Content&amp;gt; usage for more info.&lt;br /&gt;
                return &amp;#039;Loading..&amp;#039;;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Content&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you aren&amp;#039;t in the React render scope, you can specify includes via a webRequest as well:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    webRequest(&amp;#039;page/1&amp;#039;, null, {includes: [&amp;#039;creatorUser&amp;#039;]});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In some rare cases you may need a [[form|&amp;lt;Form&amp;gt;]] response to include something custom. By default edit and create include * with their response, but they don&amp;#039;t know about any nested includes that you may need. Form has a requestOpts prop which gives you the ability to set raw values on the webRequest that it makes, allowing us to essentially do the same as above but with a form instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Form action=&amp;#039;page&amp;#039; requestOpts={{includes: [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]}}&amp;gt;&lt;br /&gt;
    ..&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=API Usage=&lt;br /&gt;
&lt;br /&gt;
The frontend is actually just adding any includes on as a GET parameter, like so. If you have more than one, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
https://www.example.com/v1/page/1?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
=Nested Includes=&lt;br /&gt;
&lt;br /&gt;
You will often have situations where you want to include things on the included objects as well. This is done with a dot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;creatorUser&amp;#039;, &amp;#039;creatorUser.tags&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nesting has no depth limit - you can use 3+ layers deep if you need to. Because you&amp;#039;re spelling out each depth, there is no risk of recursion or cyclical behaviour either - you can include the creatorUser of a creatorUser, even if they are the same object. (And yes, Users do have a creatorUser as well - it can be used to identify if a user account was created by an admin for example).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Wildcards=&lt;br /&gt;
&lt;br /&gt;
You can wildcard include to show all available current level includes, and this is available nested as well:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [&amp;#039;*&amp;#039;, &amp;#039;creatorUser.*&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the wildcard is only supported as the last field (*.creatorUser is not available for example, as not everything necessarily supports the named field you want).&lt;br /&gt;
&lt;br /&gt;
Wildcards also do not show explicit includes (ones where you must explicitly list the field name).&lt;br /&gt;
&lt;br /&gt;
=Making something includable=&lt;br /&gt;
&lt;br /&gt;
Includable things are either singular objects (like the creator user - a piece of content only ever has 1 of those), or a list of things.&lt;br /&gt;
&lt;br /&gt;
==Single objects==&lt;br /&gt;
&lt;br /&gt;
To make a single object, you&amp;#039;ll need to create a &amp;quot;virtual field&amp;quot; on your C# type:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can declare as many virtual fields as you need, but essentially you define:&lt;br /&gt;
&lt;br /&gt;
* The virtual fields name (this is the text which goes inside the includes array)&lt;br /&gt;
* The type of thing that will be included. This must always be a Content type.&lt;br /&gt;
* The name of the field where the ID for your included thing will come from.&lt;br /&gt;
&lt;br /&gt;
For example, this is how creatorUser is actually defined:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[HasVirtualField(&amp;quot;CreatorUser&amp;quot;, typeof(User), &amp;quot;UserId&amp;quot;)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Create an includable field called CreatorUser, which will be a User type, and the ID of the user to include comes from the UserId field&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==Lists==&lt;br /&gt;
&lt;br /&gt;
To include a list of things on a type (for example, a list of answers on a poll), you&amp;#039;ll need to mark the C# type as listable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;)]&lt;br /&gt;
    [HasVirtualField(&amp;quot;OtherTypeName&amp;quot;, typeof(OtherTypeName), &amp;quot;OtherTypeNameId&amp;quot;)]&lt;br /&gt;
    public partial class MyCustomType : VersionedContent&amp;lt;uint&amp;gt;&lt;br /&gt;
    {&lt;br /&gt;
        public uint OtherTypeNameId;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can use multiple ListAs declarations. Users is a good example of a use case for this - there is a list of permitted users on other types, but also you might want a list of members etc.&lt;br /&gt;
&lt;br /&gt;
===Explicit Lists===&lt;br /&gt;
&lt;br /&gt;
There is a special consideration for listing though: by default, your ListAs field will be available to get included on anything. This is great for generic things like tags or categories, but the chances are you probably have something specific in mind. You can just ignore it of course, but the field will list on your admin panel and cause some unwanted clutter.&lt;br /&gt;
&lt;br /&gt;
So, to clamp down where your ListAs field actually appears, use the explicit and implicit modes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Explicit means the field name must be explicitly used - it won&amp;#039;t appear when the wildcard is used. You can, for example, do this for your includes array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
[&amp;#039;*&amp;#039;, &amp;#039;MyCustomTypes&amp;#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The wildcard is what the admin panel uses, so an explicit include will no longer appear there. So, we now have an includable field that exists but appears nowhere by default. We can then make it appear on specific types with ImplicitFor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    [ListAs(&amp;quot;MyCustomTypes&amp;quot;, Explicit=true)]&lt;br /&gt;
    [ImplicitFor(&amp;quot;MyCustomTypes&amp;quot;, typeof(ParentTypeToListOn))]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can list as many ImplicitFor attributes as you need. The above means the following:&lt;br /&gt;
&lt;br /&gt;
* This type will be includable with the name &amp;#039;MyCustomTypes&amp;#039;&lt;br /&gt;
* This &amp;#039;MyCustomTypes&amp;#039; include will not appear on wildcard inclusions&lt;br /&gt;
* But it will appear on wildcard inclusions specifically for the type called &amp;#039;ParentTypeToListOn&amp;#039;&lt;br /&gt;
&lt;br /&gt;
=On: Includes without the entity=&lt;br /&gt;
&lt;br /&gt;
There are situations where you&amp;#039;d like a list of includable things but without the thing they&amp;#039;re included on. For example, if you want a paginated list of members. Maybe it&amp;#039;s also live. Ideally we&amp;#039;d want to make use of Loop for this as it has those features, and you can do so using the On filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;user&amp;#039; filter={{On: {type: &amp;#039;MyCustomType&amp;#039;, id: 1, map: &amp;#039;UserPermits&amp;#039;}}}&amp;gt;&lt;br /&gt;
        ...&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above loop will have the same results as having included UserPermits on MyCustomType with ID #1. This allows full access to Loop features on these specialised lists of things.&lt;br /&gt;
&lt;br /&gt;
=Permissions=&lt;br /&gt;
&lt;br /&gt;
Includes are a powerful way to get simple secondary and tertiary permissions - those are permissions where being able to see something relies on being able to see something else. For example, lets say you&amp;#039;re permitted to see messages in a chat if you are a member of the chat.&lt;br /&gt;
&lt;br /&gt;
We can structure this such that the user has a userPermit in the chat, with chat list/load access restricted by the custom rule &amp;lt;code&amp;gt;HasUserPermit()&amp;lt;/code&amp;gt;, and we can restrict message list/load with the custom rule &amp;lt;code&amp;gt;IsIncluded()&amp;lt;/code&amp;gt;. This means a message can only be displayed if you arrived at it via an include, and you can only load the chat (and thus include its messages) if you have a permit. Because includes can be deeply nested, so too can these permissions. The above section about &amp;quot;On&amp;quot; is particularly useful if you want, for example, a live loop of messages which are include restricted.&lt;br /&gt;
&lt;br /&gt;
One important aspect of this is being able to create a new message with the connection to the parent chat immediately. To allow for this, &amp;quot;On&amp;quot; is supported during create calls as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
    import webRequest from &amp;#039;UI/Functions/WebRequest&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    // also from &amp;lt;Form&amp;gt;&amp;#039;s onValues&lt;br /&gt;
    webRequest(&lt;br /&gt;
        &amp;quot;chatmessage&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            text: &amp;#039;Hello! This is a chat message&amp;#039;,&lt;br /&gt;
            on: {&lt;br /&gt;
                type: &amp;#039;Chat&amp;#039;,&lt;br /&gt;
                id: 1,&lt;br /&gt;
                map: &amp;#039;Messages&amp;#039; // If ChatMessage only has one ListAs, i.e. ListAs(&amp;quot;Messages&amp;quot;), declaring map here is optional&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Public_API&amp;diff=495</id>
		<title>Public API</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Public_API&amp;diff=495"/>
		<updated>2023-03-15T15:03:57Z</updated>

		<summary type="html">&lt;p&gt;Luke: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack API&amp;#039;s (the C# binary) have a REST web API whose available endpoints originate from the set of controllers in the project. For more detail on the controllers, see the page about [[API Modules|API modules]].&lt;br /&gt;
&lt;br /&gt;
Note that it is somewhat uncommon to directly use the API via something like javascript fetch. Most of the time people use [[Loop]], [[Forms]], [[Content]] and occasionally [[webRequest]]. webRequest is a thin wrapper over fetch with a variety of conveniences built in.&lt;br /&gt;
&lt;br /&gt;
The controllers themselves are extendible - for example if you wanted /v1/user/say-hello to exist, you can do so without editing the underlying ThirdParty default UserController as it is a partial class. Controllers for entity types, which is to say for most parts of a typical SocialStack API, have some common endpoints which are as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;In all cases if you don&amp;#039;t have permission, it will error on you.&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* GET /v1/thing/list - Lists everything of this entity type.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* POST /v1/thing/list - Lists based on a POSTed [[Filters|filter]], where the post payload is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&amp;quot;query&amp;quot;:&amp;quot;Token=?&amp;quot;, &amp;quot;args&amp;quot;:[&amp;quot;the-token-value&amp;quot;]}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It can also contain pageSize, pageIndex, sort and includeTotal to fine tune the list.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&amp;quot;query&amp;quot;:&amp;quot;Token=?&amp;quot;, &amp;quot;args&amp;quot;:[&amp;quot;the-token-value&amp;quot;], &amp;quot;pageSize&amp;quot;: 10, &amp;quot;pageIndex&amp;quot;: 0, &amp;quot;includeTotal&amp;quot;: true, &amp;quot;sort&amp;quot;: {&amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;, &amp;quot;direction&amp;quot;: &amp;quot;asc&amp;quot;}}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Including the total will return the total number of results when you are receiving a partial set of them, for example because you are specifying a page size which is less than the total number of matches.&lt;br /&gt;
&lt;br /&gt;
* POST /v1/thing - Creates a new entity of the given type. The payload is simply the fields and properties of the entity.&lt;br /&gt;
&lt;br /&gt;
Properties are set as well if you need some more advanced temporary values. This is used by, for example, &amp;quot;password&amp;quot; on User which is not directly stored but instead a hash of it is.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* POST /v1/thing/14 - Where 14 is an ID. Updates the entity with the given ID. The payload is the same as for creation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* DELETE /v1/thing/14 - Deletes the given entity by its ID. Returns the entity as it was just before it was deleted.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* GET /v1/thing/14 - Loads a single item by its ID. This is what &amp;lt;[[Content]]&amp;gt; uses internally, via webRequest.&lt;br /&gt;
&lt;br /&gt;
= Includes =&lt;br /&gt;
&lt;br /&gt;
[[Includes]] are a mechanism in SocialStack for getting related data in the same API call. They are available on core endpoints such as load and list, and are simply a query string parameter of the includable fields that you would like to obtain.&lt;br /&gt;
&lt;br /&gt;
GET /v1/thing/14?includes=creatorUser&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;re not sure what includes the API currently has, you can use a wildcard:&lt;br /&gt;
&lt;br /&gt;
GET /v1/thing/14?includes=*&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If there is more than one that you would like, they are comma separated:&lt;br /&gt;
&lt;br /&gt;
GET /v1/thing/14?includes=creatorUser,tags&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Loop&amp;diff=494</id>
		<title>Loop</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Loop&amp;diff=494"/>
		<updated>2023-03-15T15:01:06Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Pagination */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;Loop&amp;gt; is a very commonly used [https://reactjs.org/docs/react-component.html React component] installed by default in SocialStack projects. It is used to loop over lists of items typically from the API.&lt;br /&gt;
&lt;br /&gt;
=Sample Usage=&lt;br /&gt;
&lt;br /&gt;
Simple loop over everything of a given type (user in this case). Note that it will always be restricted by what you are permitted to see.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039;&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally you can filter your results based on some particular [[Filters|where filter]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;page&amp;#039; filter={&lt;br /&gt;
       {&lt;br /&gt;
              where: {&lt;br /&gt;
                     url: {&lt;br /&gt;
                            startsWith: &amp;#039;/en-admin/&amp;#039;&lt;br /&gt;
                     }&lt;br /&gt;
              }&lt;br /&gt;
       }&lt;br /&gt;
     }&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           page =&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{page.title}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Live loops=&lt;br /&gt;
&lt;br /&gt;
Sometimes you&amp;#039;ll want a loop to update in realtime - whenever someone else on the site sends you a message, for example. By default any loop can update live like this, and all it takes is the live prop:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039; live&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Try creating a new user account - you&amp;#039;ll see it appear on this loop without needing to do anything else, again assuming that you are permitted to see the content.&lt;br /&gt;
&lt;br /&gt;
=Sorting=&lt;br /&gt;
&lt;br /&gt;
Filters support an order clause - see the [[Filters|page on filters]] for more information here.&lt;br /&gt;
&lt;br /&gt;
=Pagination=&lt;br /&gt;
&lt;br /&gt;
A loop can be automatically paginated using the UI/Paginator component. This is done using the paged prop:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;Loop over=&amp;#039;user&amp;#039; paged={10}&amp;gt;&lt;br /&gt;
       {&lt;br /&gt;
           user=&amp;gt; {&lt;br /&gt;
                  return &amp;lt;p&amp;gt;{user.username}&amp;lt;/p&amp;gt;;&lt;br /&gt;
           }&lt;br /&gt;
       }&lt;br /&gt;
     &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Paged itself has multiple configuration options, including the ability to specify which pagination component to use. At its basics though, just setting it to the number of items to display per page will get things moving.&lt;br /&gt;
&lt;br /&gt;
For advanced usage, the available configuration options are like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop paged={{&lt;br /&gt;
    pageSize: 10,&lt;br /&gt;
    showInput: false,&lt;br /&gt;
    maxLinks: 3,&lt;br /&gt;
    module: &amp;#039;UI/MyCustomPaginator&amp;#039;&lt;br /&gt;
}} ...&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The module option should be used carefully to avoid inconsistency (i.e. a need to specify it on every loop would potentially arise). Instead, it&amp;#039;s recommended to uninstall the default UI/Paginator and create a custom component of the same name.&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like to restrict the result count but without a paginator visible, you can directly set pageSize and pageIndex into the filter. This is effectively what the paged prop does, along with displaying its additional UI.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;dart&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Loop filter={{&lt;br /&gt;
    pageSize: 10,&lt;br /&gt;
    pageIndex: 0 // optional&lt;br /&gt;
}} ...&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Includes=&lt;br /&gt;
&lt;br /&gt;
[[Includes]] are a mechanism in SocialStack for obtaining related data in the same API call. They are a feature of the [[public API]] but are exposed via Loop too:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;page&amp;#039; includes={[&amp;#039;creatorUser&amp;#039;]}&amp;gt;&lt;br /&gt;
        {page =&amp;gt; {&lt;br /&gt;
            return &amp;lt;div&amp;gt;&lt;br /&gt;
                The page &amp;quot;{page.title}&amp;quot; was created by {page.creatorUser.username}&lt;br /&gt;
            &amp;lt;/div&amp;gt;;&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Looping over just included items =&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;d like to have live includes or loop over items which are &amp;quot;on&amp;quot; something else, then you can use the on filter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
    import Loop from &amp;#039;UI/Loop&amp;#039;;&lt;br /&gt;
    &lt;br /&gt;
    ...&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;Loop over=&amp;#039;tag&amp;#039; filter={{on: {type: &amp;#039;Page&amp;#039;, id: 4}}}&amp;gt;&lt;br /&gt;
        {tag =&amp;gt; {&lt;br /&gt;
            return {tag.name};&lt;br /&gt;
        }}&lt;br /&gt;
    &amp;lt;/Loop&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unlike regular includes, these loops support going live too.&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=493</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=493"/>
		<updated>2023-03-10T16:33:58Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Deployment rotation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=492</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=492"/>
		<updated>2023-03-10T16:32:30Z</updated>

		<summary type="html">&lt;p&gt;Luke: /* Deploying builds */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>Luke</name></author>
	</entry>
</feed>