Browse Source

Contributors

pull/1/head
Sebastian 10 years ago
parent
commit
4870233ce8
  1. 19
      Squidex.sln.DotSettings
  2. 2
      src/Squidex.Events/Apps/AppContributorAssigned.cs
  3. 19
      src/Squidex.Events/Apps/AppContributorRemoved.cs
  4. 79
      src/Squidex.Infrastructure/Language.cs
  5. 7
      src/Squidex.Infrastructure/Reflection/SimpleMapper.cs
  6. 185
      src/Squidex.Infrastructure/language-codes.csv
  7. 5
      src/Squidex.Infrastructure/project.json
  8. 21
      src/Squidex.Read/Users/IUserEntity.cs
  9. 20
      src/Squidex.Read/Users/Repositories/IUserRepository.cs
  10. 9
      src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs
  11. 6
      src/Squidex.Store.MongoDb/MongoDbModule.cs
  12. 44
      src/Squidex.Store.MongoDb/Users/MongoUserEntity.cs
  13. 45
      src/Squidex.Store.MongoDb/Users/MongoUserRepository.cs
  14. 28
      src/Squidex.Write/AppAggregateCommand.cs
  15. 42
      src/Squidex.Write/Apps/AppCommandHandler.cs
  16. 84
      src/Squidex.Write/Apps/AppDomainObject.cs
  17. 29
      src/Squidex.Write/Apps/Commands/AssignContributor.cs
  18. 2
      src/Squidex.Write/Apps/Commands/CreateApp.cs
  19. 26
      src/Squidex.Write/Apps/Commands/RemoveContributor.cs
  20. 2
      src/Squidex.Write/Schemas/Commands/AddField.cs
  21. 2
      src/Squidex.Write/Schemas/Commands/CreateSchema.cs
  22. 2
      src/Squidex.Write/Schemas/SchemaCommandHandler.cs
  23. 40
      src/Squidex.Write/Schemas/SchemaDomainObject.cs
  24. 4
      src/Squidex.Write/project.json
  25. 70
      src/Squidex/Modules/Api/Apps/AppContributorsController.cs
  26. 19
      src/Squidex/Modules/Api/Apps/Models/AppContributorDto.cs
  27. 19
      src/Squidex/Modules/Api/Apps/Models/AssignContributorDto.cs
  28. 30
      src/Squidex/Modules/Api/Languages/LanguagesController.cs
  29. 4
      src/Squidex/Modules/Api/Schemas/SchemasController.cs
  30. 19
      src/Squidex/Modules/Api/Users/Models/UserDto.cs
  31. 58
      src/Squidex/Modules/Api/Users/UsersController.cs
  32. 8
      src/Squidex/Squidex.xproj
  33. 2
      src/Squidex/Startup.cs
  34. 2
      src/Squidex/Views/Account/Login.cshtml
  35. 12
      src/Squidex/app/components/layout/app-form.component.html
  36. 2
      src/Squidex/app/components/layout/apps-menu.component.html
  37. 6
      src/Squidex/app/framework/angular/animations.ts
  38. 3
      src/Squidex/project.json
  39. 56
      tests/Squidex.Infrastructure.Tests/LanguageTests.cs
  40. 87
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  41. 91
      tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs
  42. 29
      tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs
  43. 65
      tests/Squidex.Write.Tests/Utils/HandlerTestBase.cs
  44. 2
      tests/Squidex.Write.Tests/project.json

19
Squidex.sln.DotSettings

@ -6,13 +6,16 @@
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002A_002Fapp_002F_002A_002A_002F_002A_002Ejs/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecshtml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecss/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ed_002Ets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ehtml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002Aapp_002F_002A_002A_002F_002A_002Ejs/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejson/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Escss/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_005Ftest_002Doutput_002A/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_005Ftest_002Doutput_002A/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexedValue"></s:String>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=61F6BBCE_002DA080_002D4400_002DB194_002D70E2F5D2096E_002Fd_003A_005Ftest_002Doutput/@EntryIndexRemoved">True</s:Boolean>
@ -36,19 +39,15 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">=========================================================================&#xD;
$FILENAME$&#xD;
Squidex Headless CMS&#xD;
==========================================================================&#xD;
Copyright (c) Squidex Group&#xD;
All rights reserved.&#xD;
==========================================================================</s:String>
==========================================================================&#xD;
</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
@ -91,8 +90,4 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/TypeScriptCodeStyle/ExplicitPublicModifier/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
src/Squidex.Events/Apps/AppContributorAssigned.cs

@ -15,7 +15,7 @@ namespace Squidex.Events.Apps
[TypeName("AppContributorAssigned")]
public class AppContributorAssigned : IEvent
{
public string SubjectId { get; set; }
public string ContributorId { get; set; }
public PermissionLevel Permission { get; set; }
}

19
src/Squidex.Events/Apps/AppContributorRemoved.cs

@ -0,0 +1,19 @@
// ==========================================================================
// AppContributorRemoved.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Apps
{
[TypeName("AppContributorRemoved")]
public class AppContributorRemoved : IEvent
{
public string ContributorId { get; set; }
}
}

79
src/Squidex.Infrastructure/Language.cs

@ -0,0 +1,79 @@
// =========================================================================
// Language.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace Squidex.Infrastructure
{
public sealed class Language
{
private readonly string iso2Code;
private readonly string englishName;
private static readonly Dictionary<string, Language> allLanguages = new Dictionary<string, Language>();
static Language()
{
var resourceAssembly = typeof(Language).GetTypeInfo().Assembly;
var resourceStream = resourceAssembly.GetManifestResourceStream("Squidex.Infrastructure.language-codes.csv");
using (var reader = new StreamReader(resourceStream, Encoding.UTF8))
{
reader.ReadLine();
while (!reader.EndOfStream)
{
var languageLine = reader.ReadLine();
var languageIso2Code = languageLine.Substring(1, 2);
var languageEnglishName = languageLine.Substring(6, languageLine.Length - 7);
allLanguages[languageIso2Code] = new Language(languageIso2Code, languageEnglishName);
}
}
}
public static Language GetLanguage(string iso2Code)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
try
{
return allLanguages[iso2Code];
}
catch (KeyNotFoundException)
{
throw new NotSupportedException($"Language {iso2Code} is not supported");
}
}
public static IEnumerable<Language> AllLanguages
{
get { return allLanguages.Values; }
}
public string EnglishName
{
get { return englishName; }
}
public string Iso2Code
{
get { return iso2Code; }
}
private Language(string iso2Code, string englishName)
{
this.iso2Code = iso2Code;
this.englishName = englishName;
}
}
}

7
src/Squidex.Infrastructure/Reflection/SimpleMapper.cs

@ -153,6 +153,13 @@ namespace Squidex.Infrastructure.Reflection
}
}
public static TDestination Map<TSource, TDestination>(TSource source)
where TSource : class
where TDestination : class, new()
{
return Map(source, new TDestination(), CultureInfo.CurrentCulture);
}
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
where TSource : class
where TDestination : class

185
src/Squidex.Infrastructure/language-codes.csv

@ -0,0 +1,185 @@
"alpha2","English"
"aa","Afar"
"ab","Abkhazian"
"ae","Avestan"
"af","Afrikaans"
"ak","Akan"
"am","Amharic"
"an","Aragonese"
"ar","Arabic"
"as","Assamese"
"av","Avaric"
"ay","Aymara"
"az","Azerbaijani"
"ba","Bashkir"
"be","Belarusian"
"bg","Bulgarian"
"bh","Bihari languages"
"bi","Bislama"
"bm","Bambara"
"bn","Bengali"
"bo","Tibetan"
"br","Breton"
"bs","Bosnian"
"ca","Catalan; Valencian"
"ce","Chechen"
"ch","Chamorro"
"co","Corsican"
"cr","Cree"
"cs","Czech"
"cu","Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"
"cv","Chuvash"
"cy","Welsh"
"da","Danish"
"de","German"
"dv","Divehi; Dhivehi; Maldivian"
"dz","Dzongkha"
"ee","Ewe"
"el","Greek, Modern (1453-)"
"en","English"
"eo","Esperanto"
"es","Spanish; Castilian"
"et","Estonian"
"eu","Basque"
"fa","Persian"
"ff","Fulah"
"fi","Finnish"
"fj","Fijian"
"fo","Faroese"
"fr","French"
"fy","Western Frisian"
"ga","Irish"
"gd","Gaelic; Scottish Gaelic"
"gl","Galician"
"gn","Guarani"
"gu","Gujarati"
"gv","Manx"
"ha","Hausa"
"he","Hebrew"
"hi","Hindi"
"ho","Hiri Motu"
"hr","Croatian"
"ht","Haitian; Haitian Creole"
"hu","Hungarian"
"hy","Armenian"
"hz","Herero"
"ia","Interlingua (International Auxiliary Language Association)"
"id","Indonesian"
"ie","Interlingue; Occidental"
"ig","Igbo"
"ii","Sichuan Yi; Nuosu"
"ik","Inupiaq"
"io","Ido"
"is","Icelandic"
"it","Italian"
"iu","Inuktitut"
"ja","Japanese"
"jv","Javanese"
"ka","Georgian"
"kg","Kongo"
"ki","Kikuyu; Gikuyu"
"kj","Kuanyama; Kwanyama"
"kk","Kazakh"
"kl","Kalaallisut; Greenlandic"
"km","Central Khmer"
"kn","Kannada"
"ko","Korean"
"kr","Kanuri"
"ks","Kashmiri"
"ku","Kurdish"
"kv","Komi"
"kw","Cornish"
"ky","Kirghiz; Kyrgyz"
"la","Latin"
"lb","Luxembourgish; Letzeburgesch"
"lg","Ganda"
"li","Limburgan; Limburger; Limburgish"
"ln","Lingala"
"lo","Lao"
"lt","Lithuanian"
"lu","Luba-Katanga"
"lv","Latvian"
"mg","Malagasy"
"mh","Marshallese"
"mi","Maori"
"mk","Macedonian"
"ml","Malayalam"
"mn","Mongolian"
"mr","Marathi"
"ms","Malay"
"mt","Maltese"
"my","Burmese"
"na","Nauru"
"nb","Bokmål, Norwegian; Norwegian Bokmål"
"nd","Ndebele, North; North Ndebele"
"ne","Nepali"
"ng","Ndonga"
"nl","Dutch; Flemish"
"nn","Norwegian Nynorsk; Nynorsk, Norwegian"
"no","Norwegian"
"nr","Ndebele, South; South Ndebele"
"nv","Navajo; Navaho"
"ny","Chichewa; Chewa; Nyanja"
"oc","Occitan (post 1500); Provençal"
"oj","Ojibwa"
"om","Oromo"
"or","Oriya"
"os","Ossetian; Ossetic"
"pa","Panjabi; Punjabi"
"pi","Pali"
"pl","Polish"
"ps","Pushto; Pashto"
"pt","Portuguese"
"qu","Quechua"
"rm","Romansh"
"rn","Rundi"
"ro","Romanian; Moldavian; Moldovan"
"ru","Russian"
"rw","Kinyarwanda"
"sa","Sanskrit"
"sc","Sardinian"
"sd","Sindhi"
"se","Northern Sami"
"sg","Sango"
"si","Sinhala; Sinhalese"
"sk","Slovak"
"sl","Slovenian"
"sm","Samoan"
"sn","Shona"
"so","Somali"
"sq","Albanian"
"sr","Serbian"
"ss","Swati"
"st","Sotho, Southern"
"su","Sundanese"
"sv","Swedish"
"sw","Swahili"
"ta","Tamil"
"te","Telugu"
"tg","Tajik"
"th","Thai"
"ti","Tigrinya"
"tk","Turkmen"
"tl","Tagalog"
"tn","Tswana"
"to","Tonga (Tonga Islands)"
"tr","Turkish"
"ts","Tsonga"
"tt","Tatar"
"tw","Twi"
"ty","Tahitian"
"ug","Uighur; Uyghur"
"uk","Ukrainian"
"ur","Urdu"
"uz","Uzbek"
"ve","Venda"
"vi","Vietnamese"
"vo","Volapük"
"wa","Walloon"
"wo","Wolof"
"xh","Xhosa"
"yi","Yiddish"
"yo","Yoruba"
"za","Zhuang; Chuang"
"zh","Chinese"
"zu","Zulu"
1 alpha2 English
2 aa Afar
3 ab Abkhazian
4 ae Avestan
5 af Afrikaans
6 ak Akan
7 am Amharic
8 an Aragonese
9 ar Arabic
10 as Assamese
11 av Avaric
12 ay Aymara
13 az Azerbaijani
14 ba Bashkir
15 be Belarusian
16 bg Bulgarian
17 bh Bihari languages
18 bi Bislama
19 bm Bambara
20 bn Bengali
21 bo Tibetan
22 br Breton
23 bs Bosnian
24 ca Catalan; Valencian
25 ce Chechen
26 ch Chamorro
27 co Corsican
28 cr Cree
29 cs Czech
30 cu Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic
31 cv Chuvash
32 cy Welsh
33 da Danish
34 de German
35 dv Divehi; Dhivehi; Maldivian
36 dz Dzongkha
37 ee Ewe
38 el Greek, Modern (1453-)
39 en English
40 eo Esperanto
41 es Spanish; Castilian
42 et Estonian
43 eu Basque
44 fa Persian
45 ff Fulah
46 fi Finnish
47 fj Fijian
48 fo Faroese
49 fr French
50 fy Western Frisian
51 ga Irish
52 gd Gaelic; Scottish Gaelic
53 gl Galician
54 gn Guarani
55 gu Gujarati
56 gv Manx
57 ha Hausa
58 he Hebrew
59 hi Hindi
60 ho Hiri Motu
61 hr Croatian
62 ht Haitian; Haitian Creole
63 hu Hungarian
64 hy Armenian
65 hz Herero
66 ia Interlingua (International Auxiliary Language Association)
67 id Indonesian
68 ie Interlingue; Occidental
69 ig Igbo
70 ii Sichuan Yi; Nuosu
71 ik Inupiaq
72 io Ido
73 is Icelandic
74 it Italian
75 iu Inuktitut
76 ja Japanese
77 jv Javanese
78 ka Georgian
79 kg Kongo
80 ki Kikuyu; Gikuyu
81 kj Kuanyama; Kwanyama
82 kk Kazakh
83 kl Kalaallisut; Greenlandic
84 km Central Khmer
85 kn Kannada
86 ko Korean
87 kr Kanuri
88 ks Kashmiri
89 ku Kurdish
90 kv Komi
91 kw Cornish
92 ky Kirghiz; Kyrgyz
93 la Latin
94 lb Luxembourgish; Letzeburgesch
95 lg Ganda
96 li Limburgan; Limburger; Limburgish
97 ln Lingala
98 lo Lao
99 lt Lithuanian
100 lu Luba-Katanga
101 lv Latvian
102 mg Malagasy
103 mh Marshallese
104 mi Maori
105 mk Macedonian
106 ml Malayalam
107 mn Mongolian
108 mr Marathi
109 ms Malay
110 mt Maltese
111 my Burmese
112 na Nauru
113 nb Bokmål, Norwegian; Norwegian Bokmål
114 nd Ndebele, North; North Ndebele
115 ne Nepali
116 ng Ndonga
117 nl Dutch; Flemish
118 nn Norwegian Nynorsk; Nynorsk, Norwegian
119 no Norwegian
120 nr Ndebele, South; South Ndebele
121 nv Navajo; Navaho
122 ny Chichewa; Chewa; Nyanja
123 oc Occitan (post 1500); Provençal
124 oj Ojibwa
125 om Oromo
126 or Oriya
127 os Ossetian; Ossetic
128 pa Panjabi; Punjabi
129 pi Pali
130 pl Polish
131 ps Pushto; Pashto
132 pt Portuguese
133 qu Quechua
134 rm Romansh
135 rn Rundi
136 ro Romanian; Moldavian; Moldovan
137 ru Russian
138 rw Kinyarwanda
139 sa Sanskrit
140 sc Sardinian
141 sd Sindhi
142 se Northern Sami
143 sg Sango
144 si Sinhala; Sinhalese
145 sk Slovak
146 sl Slovenian
147 sm Samoan
148 sn Shona
149 so Somali
150 sq Albanian
151 sr Serbian
152 ss Swati
153 st Sotho, Southern
154 su Sundanese
155 sv Swedish
156 sw Swahili
157 ta Tamil
158 te Telugu
159 tg Tajik
160 th Thai
161 ti Tigrinya
162 tk Turkmen
163 tl Tagalog
164 tn Tswana
165 to Tonga (Tonga Islands)
166 tr Turkish
167 ts Tsonga
168 tt Tatar
169 tw Twi
170 ty Tahitian
171 ug Uighur; Uyghur
172 uk Ukrainian
173 ur Urdu
174 uz Uzbek
175 ve Venda
176 vi Vietnamese
177 vo Volapük
178 wa Walloon
179 wo Wolof
180 xh Xhosa
181 yi Yiddish
182 yo Yoruba
183 za Zhuang; Chuang
184 zh Chinese
185 zu Zulu

5
src/Squidex.Infrastructure/project.json

@ -22,6 +22,11 @@
}
}
},
"buildOptions": {
"embed": [
"*.csv"
]
},
"tooling": {
"defaultNamespace": "Squidex.Infrastructure"
}

21
src/Squidex.Read/Users/IUserEntity.cs

@ -0,0 +1,21 @@
// ==========================================================================
// IUserEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Read.Users
{
public interface IUserEntity
{
string Id { get; }
string Email { get; }
string PictureUrl { get; }
string DisplayName { get; }
}
}

20
src/Squidex.Read/Users/Repositories/IUserRepository.cs

@ -0,0 +1,20 @@
// ==========================================================================
// IUserRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Read.Users.Repositories
{
public interface IUserRepository
{
Task<List<IUserEntity>> FindUsersByEmail(string email);
Task<IUserEntity> FindUserByIdAsync(string id);
}
}

9
src/Squidex.Store.MongoDb/Apps/MongoAppRepository.cs

@ -59,15 +59,20 @@ namespace Squidex.Store.MongoDb.Apps
return Collection.CreateAsync(headers, a => SimpleMapper.Map(@event, a));
}
public Task On(AppContributorRemoved @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(headers, a => a.Contributors.RemoveAll(c => c.SubjectId == @event.ContributorId));
}
public Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(headers, a =>
{
var contributor = a.Contributors.Find(x => x.SubjectId == @event.SubjectId);
var contributor = a.Contributors.Find(x => x.SubjectId == @event.ContributorId);
if (contributor == null)
{
contributor = new MongoAppContributorEntity { SubjectId = @event.SubjectId };
contributor = new MongoAppContributorEntity { SubjectId = @event.ContributorId };
a.Contributors.Add(contributor);
}

6
src/Squidex.Store.MongoDb/MongoDbModule.cs

@ -16,9 +16,11 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.EventStore;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Users.Repositories;
using Squidex.Store.MongoDb.Apps;
using Squidex.Store.MongoDb.Infrastructure;
using Squidex.Store.MongoDb.Schemas;
using Squidex.Store.MongoDb.Users;
namespace Squidex.Store.MongoDb
{
@ -63,6 +65,10 @@ namespace Squidex.Store.MongoDb
.As<IStreamPositionStorage>()
.SingleInstance();
builder.RegisterType<MongoUserRepository>()
.As<IUserRepository>()
.SingleInstance();
builder.RegisterType<MongoSchemaRepository>()
.As<ISchemaRepository>()
.As<ICatchEventConsumer>()

44
src/Squidex.Store.MongoDb/Users/MongoUserEntity.cs

@ -0,0 +1,44 @@
// ==========================================================================
// MongoUserEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Identity.MongoDB;
using Squidex.Infrastructure.Security;
using Squidex.Read.Users;
namespace Squidex.Store.MongoDb.Users
{
public class MongoUserEntity : IUserEntity
{
private readonly IdentityUser inner;
public string Id
{
get { return inner.Id; }
}
public string Email
{
get { return inner.Email; }
}
public string DisplayName
{
get { return inner.Claims.Find(x => x.Type == ExtendedClaimTypes.SquidexDisplayName)?.Value; }
}
public string PictureUrl
{
get { return inner.Claims.Find(x => x.Type == ExtendedClaimTypes.SquidexPictureUrl)?.Value; }
}
public MongoUserEntity(IdentityUser inner)
{
this.inner = inner;
}
}
}

45
src/Squidex.Store.MongoDb/Users/MongoUserRepository.cs

@ -0,0 +1,45 @@
// ==========================================================================
// MongoUserRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB;
using Squidex.Infrastructure;
using Squidex.Read.Users;
using Squidex.Read.Users.Repositories;
namespace Squidex.Store.MongoDb.Users
{
public sealed class MongoUserRepository : IUserRepository
{
private readonly UserManager<IdentityUser> userManager;
public MongoUserRepository(UserManager<IdentityUser> userManager)
{
Guard.NotNull(userManager, nameof(userManager));
this.userManager = userManager;
}
public Task<List<IUserEntity>> FindUsersByEmail(string email)
{
var users = userManager.Users.Where(x => x.NormalizedEmail.Contains(email)).Take(10).ToList();
return Task.FromResult(users.Select(x => (IUserEntity)new MongoUserEntity(x)).ToList());
}
public async Task<IUserEntity> FindUserByIdAsync(string id)
{
var user = await userManager.FindByIdAsync(id);
return user != null ? new MongoUserEntity(user) : null;
}
}
}

28
src/Squidex.Write/AppAggregateCommand.cs

@ -0,0 +1,28 @@
// ==========================================================================
// AppAggregateCommand.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write
{
public class AppAggregateCommand : AggregateCommand, IAppCommand
{
Guid IAppCommand.AppId
{
get
{
return AggregateId;
}
set
{
AggregateId = value;
}
}
}
}

42
src/Squidex.Write/Apps/AppCommandHandler.cs

@ -11,6 +11,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Users.Repositories;
using Squidex.Write.Apps.Commands;
namespace Squidex.Write.Apps
@ -18,28 +19,55 @@ namespace Squidex.Write.Apps
public class AppCommandHandler : CommandHandler<AppDomainObject>
{
private readonly IAppRepository appRepository;
private readonly IUserRepository userRepository;
public AppCommandHandler(
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository,
IAppRepository appRepository)
IAppRepository appRepository,
IUserRepository userRepository)
: base(domainObjectFactory, domainObjectRepository)
{
Guard.NotNull(appRepository, nameof(appRepository));
Guard.NotNull(userRepository, nameof(userRepository));
this.appRepository = appRepository;
this.userRepository = userRepository;
}
public async Task On(CreateApp command)
public Task On(CreateApp command)
{
if (await appRepository.FindAppByNameAsync(command.Name) != null)
return CreateAsync(command, async x =>
{
var error = new ValidationError($"A app with name '{command.Name}' already exists", "Name");
if (await appRepository.FindAppByNameAsync(command.Name) != null)
{
var error = new ValidationError($"A app with name '{command.Name}' already exists", nameof(CreateApp.Name));
throw new ValidationException("Cannot create a new app", error);
}
throw new ValidationException("Cannot create a new app", error);
}
await CreateAsync(command, x => x.Create(command));
x.Create(command);
});
}
public Task On(AssignContributor command)
{
return UpdateAsync(command, async x =>
{
if (await userRepository.FindUserByIdAsync(command.ContributorId) == null)
{
var error = new ValidationError($"Cannot find contributor '{command.ContributorId}", nameof(AssignContributor.ContributorId));
throw new ValidationException("Cannot assign contributor to app", error);
}
x.AssignContributor(command);
});
}
public Task On(RemoveContributor command)
{
return UpdateAsync(command, x => x.RemoveContributor(command));
}
public override Task<bool> HandleAsync(CommandContext context)

84
src/Squidex.Write/Apps/AppDomainObject.cs

@ -13,13 +13,17 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
using Squidex.Write.Apps.Commands;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure.Reflection;
// ReSharper disable InvertIf
namespace Squidex.Write.Apps
{
public sealed class AppDomainObject : DomainObject
{
private readonly Dictionary<string, PermissionLevel> contributors = new Dictionary<string, PermissionLevel>();
private string name;
public string Name
@ -27,6 +31,11 @@ namespace Squidex.Write.Apps
get { return name; }
}
public IReadOnlyDictionary<string, PermissionLevel> Contributors
{
get { return contributors; }
}
public AppDomainObject(Guid id, int version) : base(id, version)
{
}
@ -36,19 +45,64 @@ namespace Squidex.Write.Apps
name = @event.Name;
}
public void On(AppContributorAssigned @event)
{
contributors[@event.ContributorId] = @event.Permission;
}
public void On(AppContributorRemoved @event)
{
contributors.Remove(@event.ContributorId);
}
protected override void DispatchEvent(Envelope<IEvent> @event)
{
this.DispatchAction(@event.Payload);
}
public void Create(CreateApp command)
public AppDomainObject Create(CreateApp command)
{
Guard.Valid(command, nameof(command), () => "Cannot create app");
VerifyNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppCreated()));
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned { Permission = PermissionLevel.Owner }));
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned { ContributorId = command.SubjectId, Permission = PermissionLevel.Owner }));
return this;
}
public AppDomainObject AssignContributor(AssignContributor command)
{
Guard.Valid(command, nameof(command), () => "Cannot assign contributor");
VerifyCreated();
VerifyHasStillOwner(c => c[command.ContributorId] = command.Permission);
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned()));
return this;
}
public AppDomainObject RemoveContributor(RemoveContributor command)
{
Guard.Valid(command, nameof(command), () => "Cannot remove contributor");
VerifyCreated();
VerifyContributorFound(command);
VerifyHasStillOwner(c => c.Remove(command.ContributorId));
RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved()));
return this;
}
private void VerifyCreated()
{
if (string.IsNullOrWhiteSpace(name))
{
throw new DomainException("App has not been created.");
}
}
private void VerifyNotCreated()
@ -58,5 +112,29 @@ namespace Squidex.Write.Apps
throw new DomainException("App has already been created.");
}
}
private void VerifyContributorFound(RemoveContributor command)
{
if (!contributors.ContainsKey(command.ContributorId))
{
var error = new ValidationError("Contributor is not part of the app", "ContributorId");
throw new ValidationException("Cannot remove contributor", error);
}
}
private void VerifyHasStillOwner(Action<Dictionary<string, PermissionLevel>> change)
{
var contributorsCopy = new Dictionary<string, PermissionLevel>(contributors);
change(contributorsCopy);
if (contributorsCopy.All(x => x.Value != PermissionLevel.Owner))
{
var error = new ValidationError("Contributor is the last owner", "ContributorId");
throw new ValidationException("Cannot assign contributor", error);
}
}
}
}

29
src/Squidex.Write/Apps/Commands/AssignContributor.cs

@ -0,0 +1,29 @@
// ==========================================================================
// AssignContributor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Core.Apps;
using Squidex.Infrastructure;
namespace Squidex.Write.Apps.Commands
{
public class AssignContributor : AppAggregateCommand, IValidatable
{
public string ContributorId { get; set; }
public PermissionLevel Permission { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(ContributorId))
{
errors.Add(new ValidationError("Contributor id not assigned", nameof (ContributorId)));
}
}
}
}

2
src/Squidex.Write/Apps/Commands/CreateApp.cs

@ -22,7 +22,7 @@ namespace Squidex.Write.Apps.Commands
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
errors.Add(new ValidationError("DisplayName must be a valid slug", nameof(Name)));
}
}
}

26
src/Squidex.Write/Apps/Commands/RemoveContributor.cs

@ -0,0 +1,26 @@
// ==========================================================================
// RemoveContributor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Write.Apps.Commands
{
public class RemoveContributor : AppAggregateCommand, IValidatable
{
public string ContributorId { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (string.IsNullOrWhiteSpace(ContributorId))
{
errors.Add(new ValidationError("Contributor id not assigned", nameof(ContributorId)));
}
}
}
}

2
src/Squidex.Write/Schemas/Commands/AddField.cs

@ -24,7 +24,7 @@ namespace Squidex.Write.Schemas.Commands
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
errors.Add(new ValidationError("DisplayName must be a valid slug", nameof(Name)));
}
if (string.IsNullOrWhiteSpace(Type))

2
src/Squidex.Write/Schemas/Commands/CreateSchema.cs

@ -31,7 +31,7 @@ namespace Squidex.Write.Schemas.Commands
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
errors.Add(new ValidationError("DisplayName must be a valid slug", nameof(Name)));
}
}
}

2
src/Squidex.Write/Schemas/SchemaCommandHandler.cs

@ -47,7 +47,7 @@ namespace Squidex.Write.Schemas
{
if (await schemaProvider.FindSchemaIdByNameAsync(command.AppId, command.Name) != null)
{
var error = new ValidationError($"A schema with name '{command.Name}' already exists", "Name");
var error = new ValidationError($"A schema with name '{command.Name}' already exists", "DisplayName");
throw new ValidationException("Cannot create a new schema", error);
}

40
src/Squidex.Write/Schemas/SchemaDomainObject.cs

@ -103,7 +103,7 @@ namespace Squidex.Write.Schemas
isDeleted = true;
}
public void AddField(AddField command, FieldProperties properties)
public SchemaDomainObject AddField(AddField command, FieldProperties properties)
{
Guard.Valid(command, nameof(command), () => $"Cannot add field to schema {Id}");
Guard.NotNull(properties, nameof(properties));
@ -111,9 +111,11 @@ namespace Squidex.Write.Schemas
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldAdded { FieldId = ++totalFields, Name = command.Name, Properties = properties });
return this;
}
public void UpdateField(UpdateField command, FieldProperties properties)
public SchemaDomainObject UpdateField(UpdateField command, FieldProperties properties)
{
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
Guard.NotNull(properties, nameof(properties));
@ -121,66 +123,84 @@ namespace Squidex.Write.Schemas
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldUpdated { FieldId = command.FieldId, Properties = properties });
return this;
}
public void Create(CreateSchema command)
public SchemaDomainObject Create(CreateSchema command)
{
Guard.Valid(command, nameof(command), () => "Cannot create schema");
VerifyNotCreated();
RaiseEvent(SimpleMapper.Map(command, new SchemaCreated()));
return this;
}
public void Update(UpdateSchema command)
public SchemaDomainObject Update(UpdateSchema command)
{
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated()));
return this;
}
public void HideField(long fieldId)
public SchemaDomainObject HideField(long fieldId)
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldHidden { FieldId = fieldId });
return this;
}
public void ShowField(long fieldId)
public SchemaDomainObject ShowField(long fieldId)
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldShown { FieldId = fieldId });
return this;
}
public void DisableField(long fieldId)
public SchemaDomainObject DisableField(long fieldId)
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldDisabled { FieldId = fieldId });
return this;
}
public void EnableField(long fieldId)
public SchemaDomainObject EnableField(long fieldId)
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldEnabled { FieldId = fieldId });
return this;
}
public void DeleteField(long fieldId)
public SchemaDomainObject DeleteField(long fieldId)
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldDeleted { FieldId = fieldId });
return this;
}
public void Delete()
public SchemaDomainObject Delete()
{
VerifyCreatedAndNotDeleted();
RaiseEvent(new SchemaDeleted());
return this;
}
private void VerifyNotCreated()

4
src/Squidex.Write/project.json

@ -2,12 +2,14 @@
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNetCore.Identity": "1.0.0",
"NETStandard.Library": "1.6.0",
"NodaTime": "2.0.0-alpha20160729",
"Squidex.Core": "1.0.0-*",
"Squidex.Events": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*",
"Squidex.Read": "1.0.0-*"
"Squidex.Read": "1.0.0-*",
"System.Linq": "4.1.0"
},
"frameworks": {

70
src/Squidex/Modules/Api/Apps/AppContributorsController.cs

@ -0,0 +1,70 @@
// ==========================================================================
// AppContributorsController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Modules.Api.Apps.Models;
using Squidex.Pipeline;
using Squidex.Read.Apps.Repositories;
using Squidex.Write.Apps.Commands;
namespace Squidex.Modules.Api.Apps
{
[Authorize]
[ApiExceptionFilter]
[ServiceFilter(typeof(AppFilterAttribute))]
[Route("apps/{app}")]
public class AppContributorsController : ControllerBase
{
private readonly IAppRepository appRepository;
public AppContributorsController(ICommandBus commandBus, IAppRepository appRepository)
: base(commandBus)
{
this.appRepository = appRepository;
}
[HttpGet]
[Route("contributors")]
public async Task<IActionResult> GetContributors(string app)
{
var entity = await appRepository.FindAppByNameAsync(app);
if (entity == null)
{
return NotFound();
}
var model = entity.Contributors.Select(x => SimpleMapper.Map(x, new AppContributorDto())).ToList();
return Ok(model);
}
[HttpPut]
[Route("contributors")]
public async Task<IActionResult> PutContributor([FromBody] AssignContributorDto model)
{
await CommandBus.PublishAsync(SimpleMapper.Map(model, new AssignContributor()));
return Ok();
}
[HttpDelete]
[Route("contributors/{contributorId}")]
public async Task<IActionResult> PutContributor(string contributorId)
{
await CommandBus.PublishAsync(new RemoveContributor { ContributorId = contributorId });
return Ok();
}
}
}

19
src/Squidex/Modules/Api/Apps/Models/AppContributorDto.cs

@ -0,0 +1,19 @@
// =========================================================================
// AppContributorDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Core.Apps;
namespace Squidex.Modules.Api.Apps.Models
{
public sealed class AppContributorDto
{
public string ContributorId { get; set; }
public PermissionLevel Permission { get; set; }
}
}

19
src/Squidex/Modules/Api/Apps/Models/AssignContributorDto.cs

@ -0,0 +1,19 @@
// ==========================================================================
// PutContributorDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Core.Apps;
namespace Squidex.Modules.Api.Apps.Models
{
public class AssignContributorDto
{
public string ContributorId { get; set; }
public PermissionLevel Permission { get; set; }
}
}

30
src/Squidex/Modules/Api/Languages/LanguagesController.cs

@ -0,0 +1,30 @@
// =========================================================================
// LanguagesController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Modules.Api.Languages
{
[Authorize]
[ApiExceptionFilter]
public class LanguagesController : Controller
{
[HttpGet]
[Route("languages/")]
public IActionResult GetLanguages()
{
var model = Language.AllLanguages.ToList();
return Ok(model);
}
}
}

4
src/Squidex/Modules/Api/Schemas/SchemasController.cs

@ -56,7 +56,9 @@ namespace Squidex.Modules.Api.Schemas
return NotFound();
}
return Ok(SchemaDto.Create(entity.Schema));
var model = SchemaDto.Create(entity.Schema);
return Ok(model);
}
[HttpPost]

19
src/Squidex/Modules/Api/Users/Models/UserDto.cs

@ -0,0 +1,19 @@
// =========================================================================
// UserDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Modules.Api.Users.Models
{
public sealed class UserDto
{
public string Id { get; set; }
public string ProfileUrl { get; set; }
public string DisplayName { get; set; }
}
}

58
src/Squidex/Modules/Api/Users/UsersController.cs

@ -0,0 +1,58 @@
// =========================================================================
// UsersController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure.Reflection;
using Squidex.Modules.Api.Users.Models;
using Squidex.Pipeline;
using Squidex.Read.Users.Repositories;
namespace Squidex.Modules.Api.Users
{
[Authorize]
[ApiExceptionFilter]
public class UsersController : Controller
{
private readonly IUserRepository userRepository;
public UsersController(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
[HttpGet]
[Route("users")]
public async Task<IActionResult> GetUsers(string email)
{
var entities = await userRepository.FindUsersByEmail(email);
var model = entities.Select(x => SimpleMapper.Map(x, new UserDto())).ToList();
return Ok(model);
}
[HttpGet]
[Route("users/{id}/")]
public async Task<IActionResult> GetUser(string id)
{
var entity = await userRepository.FindUserByIdAsync(id);
if (entity == null)
{
return NotFound();
}
var model = SimpleMapper.Map(entity, new UserDto());
return Ok(model);
}
}
}

8
src/Squidex/Squidex.xproj

@ -16,11 +16,15 @@
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup>
<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>
<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<ItemGroup>
<DnxInvisibleFolder Include=".vscode\" />
<DnxInvisibleFolder Include="_test-output\" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

2
src/Squidex/Startup.cs

@ -181,7 +181,7 @@ namespace Squidex
headers.CacheControl = new CacheControlHeaderValue
{
MaxAge = TimeSpan.FromDays(60),
MaxAge = TimeSpan.FromDays(60)
};
}
});

2
src/Squidex/Views/Account/Login.cshtml

@ -20,7 +20,7 @@
<p>
@foreach (var provider in Model.ExternalProviders)
{
<button type="submit" name="provider" id="loginButton" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
<button type="submit" name="provider" id="loginButton" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayDisplayName account">@provider.AuthenticationScheme</button>
}
</p>
</div>

12
src/Squidex/app/components/layout/app-form.component.html

@ -6,26 +6,26 @@
</div>
<div class="form-group">
<label for="app-name">Name</label>
<label for="app-name">DisplayName</label>
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').dirty" [@fade]>
<div class="errors">
<span *ngIf="createForm.get('name').hasError('required')">
Name is required.
DisplayName is required.
</span>
<span *ngIf="createForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
DisplayName can not have more than 40 characters.
</span>
<span *ngIf="createForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes only.
DisplayName can contain lower case letters (a-z), numbers and dashes only.
</span>
</div>
</div>
<input type="text" class="form-control" id="app-name" formControlName="name" />
<input type="text" class="form-control" id="app-name" formControlDisplayName="name" />
<span class="form-hint">
The app name becomes part of the api url, e.g, https://<b>{{appName}}</b>.squidex.io/.<br />
The app name becomes part of the api url, e.g, https://<b>{{appDisplayName}}</b>.squidex.io/.<br />
It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later.
</span>
</div>

2
src/Squidex/app/components/layout/apps-menu.component.html

@ -1,6 +1,6 @@
<ul class="nav navbar-nav" *ngIf="apps">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appName}}</span>
<span class="nav-link dropdown-toggle" id="app-name" (click)="modalMenu.toggle()">{{appDisplayName}}</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" closeAlways="true" [@fade]>
<a class="dropdown-item all-apps" [routerLink]="['/app']">

6
src/Squidex/app/framework/angular/animations.ts

@ -1,3 +1,9 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';

3
src/Squidex/project.json

@ -36,7 +36,8 @@
"Squidex.Infrastructure": "1.0.0-*",
"Squidex.Read": "1.0.0-*",
"Squidex.Store.MongoDb": "1.0.0-*",
"Squidex.Write": "1.0.0-*"
"Squidex.Write": "1.0.0-*",
"System.Linq": "4.1.0"
},
"tools": {

56
tests/Squidex.Infrastructure.Tests/LanguageTests.cs

@ -0,0 +1,56 @@
// =========================================================================
// LanguageTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Xunit;
namespace Squidex.Infrastructure
{
public class LanguageTests
{
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Should_throw_if_getting_by_empty_key(string key)
{
Assert.Throws<ArgumentException>(() => Language.GetLanguage(key));
}
[Fact]
public void Should_throw_if_getting_by_null_key()
{
Assert.Throws<ArgumentNullException>(() => Language.GetLanguage(null));
}
[Fact]
public void Should_throw_if_getting_by_unsupported_language()
{
Assert.Throws<NotSupportedException>(() => Language.GetLanguage("xy"));
}
[Fact]
public void Should_provide_all_languages()
{
Assert.True(Language.AllLanguages.Count() > 100);
}
[Theory]
[InlineData("de", "German")]
[InlineData("en", "English")]
[InlineData("sv", "Swedish")]
[InlineData("zh", "Chinese")]
public void Should_provide_correct_english_name(string key, string englishName)
{
var language = Language.GetLanguage(key);
Assert.Equal(key, language.Iso2Code);
Assert.Equal(englishName, language.EnglishName);
}
}
}

87
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -6,32 +6,34 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Moq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Read.Apps;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Users;
using Squidex.Read.Users.Repositories;
using Squidex.Write.Apps;
using Squidex.Write.Apps.Commands;
using Squidex.Write.Tests.Utils;
using Xunit;
// ReSharper disable ImplicitlyCapturedClosure
namespace Squidex.Write.Tests.Apps
{
public class AppCommandHandlerTests
public class AppCommandHandlerTests : HandlerTestBase<AppDomainObject>
{
private readonly Mock<IDomainObjectFactory> domainObjectFactory = new Mock<IDomainObjectFactory>();
private readonly Mock<IDomainObjectRepository> domainObjectRepository = new Mock<IDomainObjectRepository>();
private readonly Mock<IAppRepository> appRepository = new Mock<IAppRepository>();
private readonly Mock<IUserRepository> userRepository = new Mock<IUserRepository>();
private readonly AppCommandHandler sut;
public AppCommandHandlerTests()
{
sut = new AppCommandHandler(
domainObjectFactory.Object,
domainObjectRepository.Object,
appRepository.Object);
DomainObjectFactory.Object,
DomainObjectRepository.Object,
appRepository.Object,
userRepository.Object);
}
[Fact]
@ -39,7 +41,10 @@ namespace Squidex.Write.Tests.Apps
{
appRepository.Setup(x => x.FindAppByNameAsync("my-app")).Returns(Task.FromResult(new Mock<IAppEntity>().Object)).Verifiable();
await Assert.ThrowsAsync<ValidationException>(async () => await sut.On(new CreateApp { Name = "my-app" }));
await TestCreate(new AppDomainObject(Id, 0), async _ =>
{
await Assert.ThrowsAsync<ValidationException>(async () => await sut.On(new CreateApp { Name = "my-app" }));
}, false);
appRepository.VerifyAll();
}
@ -47,20 +52,66 @@ namespace Squidex.Write.Tests.Apps
[Fact]
public async Task Create_should_create_app_if_name_is_free()
{
var id = Guid.NewGuid();
var app = new AppDomainObject(id, 0);
var command = new CreateApp { Name = "my-app", AggregateId = Id, SubjectId = "456" };
appRepository.Setup(x => x.FindAppByNameAsync("my-app")).Returns(Task.FromResult<IAppEntity>(null)).Verifiable();
domainObjectFactory.Setup(x => x.CreateNew(typeof(AppDomainObject), id)).Returns(app).Verifiable();
domainObjectRepository.Setup(x => x.SaveAsync(app, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable();
await sut.On(new CreateApp { Name = "my-app", AggregateId = id });
await TestCreate(new AppDomainObject(Id, 0), async _ =>
{
await sut.On(command);
});
appRepository.VerifyAll();
domainObjectFactory.VerifyAll();
domainObjectRepository.VerifyAll();
}
[Fact]
public async Task AssignContributor_should_throw_if_user_not_found()
{
var app =
new AppDomainObject(Id, 0)
.Create(new CreateApp { Name = "my-app", SubjectId = "123" });
var command = new AssignContributor { AggregateId = Id, ContributorId = "456" };
userRepository.Setup(x => x.FindUserByIdAsync(command.ContributorId)).Returns(Task.FromResult<IUserEntity>(null));
await TestUpdate(app, async _ =>
{
await Assert.ThrowsAsync<ValidationException>(() => sut.On(command));
}, false);
}
[Fact]
public async Task AssignContributor_should_assign_if_user_found()
{
var app =
new AppDomainObject(Id, 0)
.Create(new CreateApp { Name = "my-app", SubjectId = "123" });
var command = new AssignContributor { AggregateId = Id, ContributorId = "456" };
userRepository.Setup(x => x.FindUserByIdAsync(command.ContributorId)).Returns(Task.FromResult(new Mock<IUserEntity>().Object));
await TestUpdate(app, async _ =>
{
await sut.On(command);
});
}
[Fact]
public async Task RemoveContributor_should_update_domain_object()
{
var app =
new AppDomainObject(Id, 0)
.Create(new CreateApp { Name = "my-app", SubjectId = "123" })
.AssignContributor(new AssignContributor { ContributorId = "456" });
var command = new RemoveContributor { AggregateId = Id, ContributorId = "456" };
await TestUpdate(app, async _ =>
{
await sut.On(command);
});
}
}
}

91
tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

@ -9,8 +9,10 @@
using System;
using System.Linq;
using FluentAssertions;
using Squidex.Core.Apps;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Write.Apps;
using Squidex.Write.Apps.Commands;
using Xunit;
@ -21,11 +23,13 @@ namespace Squidex.Write.Tests.Apps
{
private const string TestName = "app";
private readonly AppDomainObject sut = new AppDomainObject(Guid.NewGuid(), 0);
private readonly string subjectId = Guid.NewGuid().ToString();
private readonly string contributorId = Guid.NewGuid().ToString();
[Fact]
public void Create_should_throw_if_created()
{
sut.Create(new CreateApp { Name = TestName });
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
Assert.Throws<DomainException>(() => sut.Create(new CreateApp { Name = TestName }));
}
@ -37,17 +41,90 @@ namespace Squidex.Write.Tests.Apps
}
[Fact]
public void Create_should_specify_name()
public void Create_should_specify_and_owner()
{
sut.Create(new CreateApp { Name = TestName });
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
Assert.Equal(TestName, sut.Name);
Assert.Equal(PermissionLevel.Owner, sut.Contributors[subjectId]);
sut.GetUncomittedEvents()
.Select(x => x.Payload as AppCreated)
.Single()
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new AppCreated { Name = TestName });
new IEvent[]
{
new AppCreated { Name = TestName },
new AppContributorAssigned { ContributorId = subjectId, Permission = PermissionLevel.Owner }
});
}
[Fact]
public void AssignContributor_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.AssignContributor(new AssignContributor { ContributorId = contributorId }));
}
[Fact]
public void AssignContributor_should_throw_if_single_owner_becomes_non_owner()
{
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
Assert.Throws<ValidationException>(() => sut.AssignContributor(new AssignContributor { ContributorId = subjectId, Permission = PermissionLevel.Editor }));
}
[Fact]
public void AssignContributor_should_create_events()
{
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor });
Assert.Equal(PermissionLevel.Editor, sut.Contributors[contributorId]);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(2).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new AppContributorAssigned { ContributorId = contributorId, Permission = PermissionLevel.Editor }
});
}
[Fact]
public void RemoveContributor_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.RemoveContributor(new RemoveContributor { ContributorId = contributorId }));
}
[Fact]
public void RemoveContributor_should_throw_if_all_owners_removed()
{
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
Assert.Throws<ValidationException>(() => sut.RemoveContributor(new RemoveContributor { ContributorId = subjectId }));
}
[Fact]
public void RemoveContributor_should_throw_if_contributor_not_found()
{
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor });
Assert.Throws<ValidationException>(() => sut.RemoveContributor(new RemoveContributor { ContributorId = "123" }));
}
[Fact]
public void RemoveContributor_should_create_events_and_remove_contributor()
{
sut.Create(new CreateApp { Name = TestName, SubjectId = subjectId });
sut.AssignContributor(new AssignContributor { ContributorId = contributorId, Permission = PermissionLevel.Editor });
sut.RemoveContributor(new RemoveContributor { ContributorId = contributorId });
Assert.False(sut.Contributors.ContainsKey(contributorId));
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(3).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new AppContributorRemoved { ContributorId = contributorId }
});
}
}
}

29
tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs

@ -12,6 +12,7 @@ using FluentAssertions;
using Squidex.Core.Schemas;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Write.Schemas;
using Squidex.Write.Schemas.Commands;
using Xunit;
@ -56,11 +57,12 @@ namespace Squidex.Write.Tests.Schemas
Assert.Equal(props, sut.Schema.Properties);
Assert.Equal(appId, sut.AppId);
sut.GetUncomittedEvents()
.Select(x => x.Payload as SchemaCreated)
.Single()
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new SchemaCreated { Name = TestName, AppId = appId, Properties = props });
new IEvent[]
{
new SchemaCreated { Name = TestName, AppId = appId, Properties = props }
});
}
[Fact]
@ -70,7 +72,7 @@ namespace Squidex.Write.Tests.Schemas
}
[Fact]
public void Update_should_throw_if_command_is_deleted()
public void Update_should_throw_if_schema_is_deleted()
{
sut.Create(new CreateSchema { Name = TestName });
sut.Delete();
@ -96,11 +98,12 @@ namespace Squidex.Write.Tests.Schemas
Assert.Equal(props, sut.Schema.Properties);
sut.GetUncomittedEvents()
.Select(x => x.Payload as SchemaUpdated)
.Last()
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo(
new SchemaUpdated { Properties = props });
new IEvent[]
{
new SchemaUpdated { Properties = props }
});
}
[Fact]
@ -125,7 +128,13 @@ namespace Squidex.Write.Tests.Schemas
sut.Delete();
Assert.True(sut.IsDeleted);
Assert.IsType<SchemaDeleted>(sut.GetUncomittedEvents().Last().Payload);
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new SchemaDeleted()
});
}
}
}

65
tests/Squidex.Write.Tests/Utils/HandlerTestBase.cs

@ -0,0 +1,65 @@
// ==========================================================================
// HandlerTestBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Moq;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write.Tests.Utils
{
public abstract class HandlerTestBase<T> where T : DomainObject
{
private readonly Mock<IDomainObjectFactory> domainObjectFactory = new Mock<IDomainObjectFactory>();
private readonly Mock<IDomainObjectRepository> domainObjectRepository = new Mock<IDomainObjectRepository>();
private readonly Guid id = Guid.NewGuid();
protected Guid Id
{
get { return id; }
}
protected Mock<IDomainObjectFactory> DomainObjectFactory
{
get { return domainObjectFactory; }
}
protected Mock<IDomainObjectRepository> DomainObjectRepository
{
get { return domainObjectRepository; }
}
public async Task TestCreate(T domainObject, Func<T, Task> action, bool succeeded = true)
{
domainObjectFactory.Setup(x => x.CreateNew(typeof(T), id)).Returns(domainObject).Verifiable();
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable();
await action(domainObject);
if (succeeded)
{
domainObjectFactory.VerifyAll();
domainObjectRepository.VerifyAll();
}
}
public async Task TestUpdate(T domainObject, Func<T, Task> action, bool succeeded = true)
{
domainObjectRepository.Setup(x => x.GetByIdAsync<T>(domainObject.Id, int.MaxValue)).Returns(Task.FromResult(domainObject)).Verifiable();
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable();
await action(domainObject);
if (succeeded)
{
domainObjectRepository.VerifyAll();
}
}
}
}

2
tests/Squidex.Write.Tests/project.json

@ -1,4 +1,4 @@
{
{
"buildOptions": {
"copyToOutput": {
"include": [

Loading…
Cancel
Save