├── .buildpacks ├── .env.example ├── .gitattributes ├── .gitignore ├── Docker ├── ProxyNginx.conf ├── entrypoint.sh └── php-fpm.conf ├── Dockerfile ├── LICENSE.txt ├── Procfile ├── README.md ├── app ├── Console │ ├── Commands │ │ └── .gitkeep │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── DashLicenseUtil.php │ │ ├── EntriesController.php │ │ ├── ForgotController.php │ │ ├── TeamsController.php │ │ └── UsersController.php │ ├── Middleware │ │ └── ExampleMiddleware.php │ └── routes.php ├── Jobs │ └── Job.php ├── Models │ ├── Entry.php │ ├── Identifier.php │ ├── License.php │ ├── Team.php │ ├── User.php │ └── Vote.php ├── Notifications │ └── ResetPasswordNotification.php └── Providers │ └── AppServiceProvider.php ├── artisan ├── bootstrap └── app.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── mail.php └── session.php ├── database ├── migrations │ ├── .gitkeep │ ├── 2015_01_18_140441_create_users_table.php │ ├── 2015_01_18_155040_create_password_reminders_table.php │ ├── 2015_01_22_150131_create_teams_table.php │ └── 2015_01_26_162713_create_entries_table.php └── seeds │ └── DatabaseSeeder.php ├── docker-compose.yml ├── nginx.conf ├── phpunit.xml ├── public ├── .htaccess └── index.php ├── requirements.txt ├── resources ├── lang │ └── en │ │ └── validation.php └── views │ ├── .gitkeep │ └── emails │ └── auth │ └── reminder.blade.php ├── runtime.txt ├── server.php ├── storage ├── app │ └── .gitignore ├── framework │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── ExampleTest.php └── TestCase.php /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/heroku/heroku-buildpack-php 2 | https://github.com/heroku/heroku-buildpack-python 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_KEY=SomeRandomKey! 2 | 3 | APP_ENV=production 4 | APP_DEBUG=false 5 | APP_LOCALE=en 6 | APP_FALLBACK_LOCALE=en 7 | 8 | DB_CONNECTION=mysql 9 | DB_HOST=localhost 10 | DB_DATABASE=annotations 11 | DB_USERNAME=mysql_username 12 | DB_PASSWORD=mysql_password 13 | 14 | CACHE_DRIVER=file 15 | SESSION_DRIVER=file 16 | QUEUE_DRIVER=sync 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lumen.log 2 | /vendor 3 | .env 4 | .env.production 5 | /node_modules 6 | /bootstrap/compiled.php 7 | .env.*.php 8 | .env.php 9 | annotations.dkim.* 10 | .DS_Store 11 | Thumbs.db 12 | Annotations.paw 13 | /storage/framework/compiled.php 14 | 15 | # Claude Code files 16 | CLAUDE.md 17 | .claude/ 18 | -------------------------------------------------------------------------------- /Docker/ProxyNginx.conf: -------------------------------------------------------------------------------- 1 | upstream dash-handler { 2 | server 127.0.0.1:9002; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_name dash.example.com; 8 | root /var/www/public; 9 | 10 | # Point index to the Laravel front controller. 11 | index index.php; 12 | 13 | location / { 14 | # URLs to attempt, including pretty ones. 15 | try_files $uri $uri/ /index.php?$query_string; 16 | } 17 | 18 | location ~ \.php(?:$|/) { 19 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 20 | include fastcgi_params; 21 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 22 | fastcgi_param PATH_INFO $fastcgi_path_info; 23 | fastcgi_pass dash-handler; 24 | } 25 | 26 | # Remove trailing slash to please routing system. 27 | if (!-d $request_filename) { 28 | rewrite ^/(.+)/$ /$1 permanent; 29 | } 30 | 31 | # We don't need .ht files with nginx. 32 | location ~ /\.ht { 33 | deny all; 34 | } 35 | 36 | # Set header expirations on per-project basis 37 | location ~* \.(?:ico|css|js|jpe?g|JPG|png|svg|woff)$ { 38 | expires 365d; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat > .env <<-EOF 4 | APP_KEY=${APP_KEY:-SomeRandomKey!} 5 | APP_ENV=${APP_ENV:-local} 6 | APP_DEBUG=${APP_ENV:-true} 7 | APP_LOCALE=${APP_LOCALE:-en} 8 | APP_FALLBACK_LOCALE=${APP_FALLBACK_LOCALE:-en} 9 | 10 | DB_CONNECTION=${DB_CONNECTION:-mysql} 11 | DB_HOST=${DB_PORT_3306_TCP_ADDR:-localhost} 12 | DB_DATABASE=${DB_DATABASE:-annotations} 13 | DB_USERNAME=${DB_USER:-mysql_username} 14 | DB_PASSWORD=${DB_PASSWORD:-mysql_password} 15 | 16 | CACHE_DRIVER=${CACHE_DRIVER:-file} 17 | SESSION_DRIVER=${SESSION_DRIVER:-file} 18 | QUEUE_DRIVER=${QUEUE_DRIVER:-sync} 19 | EOF 20 | composer config -g github-oauth.github.com ${TOKEN:-} 21 | composer install 22 | php artisan cache:clear 23 | chmod -R 777 public 24 | chmod -R 777 storage 25 | php artisan migrate --force 26 | exec php-fpm 27 | -------------------------------------------------------------------------------- /Docker/php-fpm.conf: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;; 2 | ; FPM Configuration ; 3 | ;;;;;;;;;;;;;;;;;;;;; 4 | 5 | ; All relative paths in this configuration file are relative to PHP's install 6 | ; prefix (/usr). This prefix can be dynamically changed by using the 7 | ; '-p' argument from the command line. 8 | 9 | ; Include one or more files. If glob(3) exists, it is used to include a bunch of 10 | ; files from a glob(3) pattern. This directive can be used everywhere in the 11 | ; file. 12 | ; Relative path can also be used. They will be prefixed by: 13 | ; - the global prefix if it's been set (-p argument) 14 | ; - /usr otherwise 15 | ;include=etc/fpm.d/*.conf 16 | 17 | ;;;;;;;;;;;;;;;;;; 18 | ; Global Options ; 19 | ;;;;;;;;;;;;;;;;;; 20 | 21 | [global] 22 | ; Pid file 23 | ; Note: the default prefix is /var 24 | ; Default Value: none 25 | ;pid = run/php-fpm.pid 26 | 27 | ; Error log file 28 | ; If it's set to "syslog", log is sent to syslogd instead of being written 29 | ; in a local file. 30 | ; Note: the default prefix is /var 31 | ; Default Value: log/php-fpm.log 32 | error_log = /proc/self/fd/2 33 | 34 | ; syslog_facility is used to specify what type of program is logging the 35 | ; message. This lets syslogd specify that messages from different facilities 36 | ; will be handled differently. 37 | ; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) 38 | ; Default Value: daemon 39 | ;syslog.facility = daemon 40 | 41 | ; syslog_ident is prepended to every message. If you have multiple FPM 42 | ; instances running on the same server, you can change the default value 43 | ; which must suit common needs. 44 | ; Default Value: php-fpm 45 | ;syslog.ident = php-fpm 46 | 47 | ; Log level 48 | ; Possible Values: alert, error, warning, notice, debug 49 | ; Default Value: notice 50 | ;log_level = notice 51 | 52 | ; If this number of child processes exit with SIGSEGV or SIGBUS within the time 53 | ; interval set by emergency_restart_interval then FPM will restart. A value 54 | ; of '0' means 'Off'. 55 | ; Default Value: 0 56 | ;emergency_restart_threshold = 0 57 | 58 | ; Interval of time used by emergency_restart_interval to determine when 59 | ; a graceful restart will be initiated. This can be useful to work around 60 | ; accidental corruptions in an accelerator's shared memory. 61 | ; Available Units: s(econds), m(inutes), h(ours), or d(ays) 62 | ; Default Unit: seconds 63 | ; Default Value: 0 64 | ;emergency_restart_interval = 0 65 | 66 | ; Time limit for child processes to wait for a reaction on signals from master. 67 | ; Available units: s(econds), m(inutes), h(ours), or d(ays) 68 | ; Default Unit: seconds 69 | ; Default Value: 0 70 | ;process_control_timeout = 0 71 | 72 | ; The maximum number of processes FPM will fork. This has been design to control 73 | ; the global number of processes when using dynamic PM within a lot of pools. 74 | ; Use it with caution. 75 | ; Note: A value of 0 indicates no limit 76 | ; Default Value: 0 77 | ; process.max = 128 78 | 79 | ; Specify the nice(2) priority to apply to the master process (only if set) 80 | ; The value can vary from -19 (highest priority) to 20 (lower priority) 81 | ; Note: - It will only work if the FPM master process is launched as root 82 | ; - The pool process will inherit the master process priority 83 | ; unless it specified otherwise 84 | ; Default Value: no set 85 | ; process.priority = -19 86 | 87 | ; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. 88 | ; Default Value: yes 89 | daemonize = no 90 | 91 | ; Set open file descriptor rlimit for the master process. 92 | ; Default Value: system defined value 93 | ;rlimit_files = 1024 94 | 95 | ; Set max core size rlimit for the master process. 96 | ; Possible Values: 'unlimited' or an integer greater or equal to 0 97 | ; Default Value: system defined value 98 | ;rlimit_core = 0 99 | 100 | ; Specify the event mechanism FPM will use. The following is available: 101 | ; - select (any POSIX os) 102 | ; - poll (any POSIX os) 103 | ; - epoll (linux >= 2.5.44) 104 | ; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) 105 | ; - /dev/poll (Solaris >= 7) 106 | ; - port (Solaris >= 10) 107 | ; Default Value: not set (auto detection) 108 | ;events.mechanism = epoll 109 | 110 | ; When FPM is build with systemd integration, specify the interval, 111 | ; in second, between health report notification to systemd. 112 | ; Set to 0 to disable. 113 | ; Available Units: s(econds), m(inutes), h(ours) 114 | ; Default Unit: seconds 115 | ; Default value: 10 116 | ;systemd_interval = 10 117 | 118 | ;;;;;;;;;;;;;;;;;;;; 119 | ; Pool Definitions ; 120 | ;;;;;;;;;;;;;;;;;;;; 121 | 122 | ; Multiple pools of child processes may be started with different listening 123 | ; ports and different management options. The name of the pool will be 124 | ; used in logs and stats. There is no limitation on the number of pools which 125 | ; FPM can handle. Your system will tell you anyway :) 126 | 127 | ; Start a new pool named 'www'. 128 | ; the variable $pool can we used in any directive and will be replaced by the 129 | ; pool name ('www' here) 130 | [www] 131 | 132 | ; Per pool prefix 133 | ; It only applies on the following directives: 134 | ; - 'access.log' 135 | ; - 'slowlog' 136 | ; - 'listen' (unixsocket) 137 | ; - 'chroot' 138 | ; - 'chdir' 139 | ; - 'php_values' 140 | ; - 'php_admin_values' 141 | ; When not set, the global prefix (or /usr) applies instead. 142 | ; Note: This directive can also be relative to the global prefix. 143 | ; Default Value: none 144 | ;prefix = /path/to/pools/$pool 145 | 146 | ; Unix user/group of processes 147 | ; Note: The user is mandatory. If the group is not set, the default user's group 148 | ; will be used. 149 | user = nobody 150 | group = nobody 151 | 152 | ; The address on which to accept FastCGI requests. 153 | ; Valid syntaxes are: 154 | ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on 155 | ; a specific port; 156 | ; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on 157 | ; a specific port; 158 | ; 'port' - to listen on a TCP socket to all IPv4 addresses on a 159 | ; specific port; 160 | ; '[::]:port' - to listen on a TCP socket to all addresses 161 | ; (IPv6 and IPv4-mapped) on a specific port; 162 | ; '/path/to/unix/socket' - to listen on a unix socket. 163 | ; Note: This value is mandatory. 164 | listen = 0.0.0.0:9000 165 | 166 | ; Set listen(2) backlog. 167 | ; Default Value: 65535 (-1 on FreeBSD and OpenBSD) 168 | ;listen.backlog = 65535 169 | 170 | ; Set permissions for unix socket, if one is used. In Linux, read/write 171 | ; permissions must be set in order to allow connections from a web server. Many 172 | ; BSD-derived systems allow connections regardless of permissions. 173 | ; Default Values: user and group are set as the running user 174 | ; mode is set to 0660 175 | ;listen.owner = nobody 176 | ;listen.group = nobody 177 | ;listen.mode = 0660 178 | ; When POSIX Access Control Lists are supported you can set them using 179 | ; these options, value is a comma separated list of user/group names. 180 | ; When set, listen.owner and listen.group are ignored 181 | ;listen.acl_users = 182 | ;listen.acl_groups = 183 | 184 | ; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. 185 | ; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original 186 | ; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address 187 | ; must be separated by a comma. If this value is left blank, connections will be 188 | ; accepted from any ip address. 189 | ; Default Value: any 190 | ;;listen.allowed_clients = 127.0.0.1 191 | 192 | ; Specify the nice(2) priority to apply to the pool processes (only if set) 193 | ; The value can vary from -19 (highest priority) to 20 (lower priority) 194 | ; Note: - It will only work if the FPM master process is launched as root 195 | ; - The pool processes will inherit the master process priority 196 | ; unless it specified otherwise 197 | ; Default Value: no set 198 | ; process.priority = -19 199 | 200 | ; Choose how the process manager will control the number of child processes. 201 | ; Possible Values: 202 | ; static - a fixed number (pm.max_children) of child processes; 203 | ; dynamic - the number of child processes are set dynamically based on the 204 | ; following directives. With this process management, there will be 205 | ; always at least 1 children. 206 | ; pm.max_children - the maximum number of children that can 207 | ; be alive at the same time. 208 | ; pm.start_servers - the number of children created on startup. 209 | ; pm.min_spare_servers - the minimum number of children in 'idle' 210 | ; state (waiting to process). If the number 211 | ; of 'idle' processes is less than this 212 | ; number then some children will be created. 213 | ; pm.max_spare_servers - the maximum number of children in 'idle' 214 | ; state (waiting to process). If the number 215 | ; of 'idle' processes is greater than this 216 | ; number then some children will be killed. 217 | ; ondemand - no children are created at startup. Children will be forked when 218 | ; new requests will connect. The following parameter are used: 219 | ; pm.max_children - the maximum number of children that 220 | ; can be alive at the same time. 221 | ; pm.process_idle_timeout - The number of seconds after which 222 | ; an idle process will be killed. 223 | ; Note: This value is mandatory. 224 | pm = dynamic 225 | 226 | ; The number of child processes to be created when pm is set to 'static' and the 227 | ; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. 228 | ; This value sets the limit on the number of simultaneous requests that will be 229 | ; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. 230 | ; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP 231 | ; CGI. The below defaults are based on a server without much resources. Don't 232 | ; forget to tweak pm.* to fit your needs. 233 | ; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' 234 | ; Note: This value is mandatory. 235 | pm.max_children = 5 236 | 237 | ; The number of child processes created on startup. 238 | ; Note: Used only when pm is set to 'dynamic' 239 | ; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 240 | pm.start_servers = 2 241 | 242 | ; The desired minimum number of idle server processes. 243 | ; Note: Used only when pm is set to 'dynamic' 244 | ; Note: Mandatory when pm is set to 'dynamic' 245 | pm.min_spare_servers = 1 246 | 247 | ; The desired maximum number of idle server processes. 248 | ; Note: Used only when pm is set to 'dynamic' 249 | ; Note: Mandatory when pm is set to 'dynamic' 250 | pm.max_spare_servers = 3 251 | 252 | ; The number of seconds after which an idle process will be killed. 253 | ; Note: Used only when pm is set to 'ondemand' 254 | ; Default Value: 10s 255 | ;pm.process_idle_timeout = 10s; 256 | 257 | ; The number of requests each child process should execute before respawning. 258 | ; This can be useful to work around memory leaks in 3rd party libraries. For 259 | ; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. 260 | ; Default Value: 0 261 | ;pm.max_requests = 500 262 | 263 | ; The URI to view the FPM status page. If this value is not set, no URI will be 264 | ; recognized as a status page. It shows the following informations: 265 | ; pool - the name of the pool; 266 | ; process manager - static, dynamic or ondemand; 267 | ; start time - the date and time FPM has started; 268 | ; start since - number of seconds since FPM has started; 269 | ; accepted conn - the number of request accepted by the pool; 270 | ; listen queue - the number of request in the queue of pending 271 | ; connections (see backlog in listen(2)); 272 | ; max listen queue - the maximum number of requests in the queue 273 | ; of pending connections since FPM has started; 274 | ; listen queue len - the size of the socket queue of pending connections; 275 | ; idle processes - the number of idle processes; 276 | ; active processes - the number of active processes; 277 | ; total processes - the number of idle + active processes; 278 | ; max active processes - the maximum number of active processes since FPM 279 | ; has started; 280 | ; max children reached - number of times, the process limit has been reached, 281 | ; when pm tries to start more children (works only for 282 | ; pm 'dynamic' and 'ondemand'); 283 | ; Value are updated in real time. 284 | ; Example output: 285 | ; pool: www 286 | ; process manager: static 287 | ; start time: 01/Jul/2011:17:53:49 +0200 288 | ; start since: 62636 289 | ; accepted conn: 190460 290 | ; listen queue: 0 291 | ; max listen queue: 1 292 | ; listen queue len: 42 293 | ; idle processes: 4 294 | ; active processes: 11 295 | ; total processes: 15 296 | ; max active processes: 12 297 | ; max children reached: 0 298 | ; 299 | ; By default the status page output is formatted as text/plain. Passing either 300 | ; 'html', 'xml' or 'json' in the query string will return the corresponding 301 | ; output syntax. Example: 302 | ; http://www.foo.bar/status 303 | ; http://www.foo.bar/status?json 304 | ; http://www.foo.bar/status?html 305 | ; http://www.foo.bar/status?xml 306 | ; 307 | ; By default the status page only outputs short status. Passing 'full' in the 308 | ; query string will also return status for each pool process. 309 | ; Example: 310 | ; http://www.foo.bar/status?full 311 | ; http://www.foo.bar/status?json&full 312 | ; http://www.foo.bar/status?html&full 313 | ; http://www.foo.bar/status?xml&full 314 | ; The Full status returns for each process: 315 | ; pid - the PID of the process; 316 | ; state - the state of the process (Idle, Running, ...); 317 | ; start time - the date and time the process has started; 318 | ; start since - the number of seconds since the process has started; 319 | ; requests - the number of requests the process has served; 320 | ; request duration - the duration in µs of the requests; 321 | ; request method - the request method (GET, POST, ...); 322 | ; request URI - the request URI with the query string; 323 | ; content length - the content length of the request (only with POST); 324 | ; user - the user (PHP_AUTH_USER) (or '-' if not set); 325 | ; script - the main script called (or '-' if not set); 326 | ; last request cpu - the %cpu the last request consumed 327 | ; it's always 0 if the process is not in Idle state 328 | ; because CPU calculation is done when the request 329 | ; processing has terminated; 330 | ; last request memory - the max amount of memory the last request consumed 331 | ; it's always 0 if the process is not in Idle state 332 | ; because memory calculation is done when the request 333 | ; processing has terminated; 334 | ; If the process is in Idle state, then informations are related to the 335 | ; last request the process has served. Otherwise informations are related to 336 | ; the current request being served. 337 | ; Example output: 338 | ; ************************ 339 | ; pid: 31330 340 | ; state: Running 341 | ; start time: 01/Jul/2011:17:53:49 +0200 342 | ; start since: 63087 343 | ; requests: 12808 344 | ; request duration: 1250261 345 | ; request method: GET 346 | ; request URI: /test_mem.php?N=10000 347 | ; content length: 0 348 | ; user: - 349 | ; script: /home/fat/web/docs/php/test_mem.php 350 | ; last request cpu: 0.00 351 | ; last request memory: 0 352 | ; 353 | ; Note: There is a real-time FPM status monitoring sample web page available 354 | ; It's available in: /usr/share/php/fpm/status.html 355 | ; 356 | ; Note: The value must start with a leading slash (/). The value can be 357 | ; anything, but it may not be a good idea to use the .php extension or it 358 | ; may conflict with a real PHP file. 359 | ; Default Value: not set 360 | ;pm.status_path = /status 361 | 362 | ; The ping URI to call the monitoring page of FPM. If this value is not set, no 363 | ; URI will be recognized as a ping page. This could be used to test from outside 364 | ; that FPM is alive and responding, or to 365 | ; - create a graph of FPM availability (rrd or such); 366 | ; - remove a server from a group if it is not responding (load balancing); 367 | ; - trigger alerts for the operating team (24/7). 368 | ; Note: The value must start with a leading slash (/). The value can be 369 | ; anything, but it may not be a good idea to use the .php extension or it 370 | ; may conflict with a real PHP file. 371 | ; Default Value: not set 372 | ;ping.path = /ping 373 | 374 | ; This directive may be used to customize the response of a ping request. The 375 | ; response is formatted as text/plain with a 200 response code. 376 | ; Default Value: pong 377 | ;ping.response = pong 378 | 379 | ; The access log file 380 | ; Default: not set 381 | access.log = /proc/self/fd/2 382 | 383 | ; The access log format. 384 | ; The following syntax is allowed 385 | ; %%: the '%' character 386 | ; %C: %CPU used by the request 387 | ; it can accept the following format: 388 | ; - %{user}C for user CPU only 389 | ; - %{system}C for system CPU only 390 | ; - %{total}C for user + system CPU (default) 391 | ; %d: time taken to serve the request 392 | ; it can accept the following format: 393 | ; - %{seconds}d (default) 394 | ; - %{miliseconds}d 395 | ; - %{mili}d 396 | ; - %{microseconds}d 397 | ; - %{micro}d 398 | ; %e: an environment variable (same as $_ENV or $_SERVER) 399 | ; it must be associated with embraces to specify the name of the env 400 | ; variable. Some exemples: 401 | ; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e 402 | ; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e 403 | ; %f: script filename 404 | ; %l: content-length of the request (for POST request only) 405 | ; %m: request method 406 | ; %M: peak of memory allocated by PHP 407 | ; it can accept the following format: 408 | ; - %{bytes}M (default) 409 | ; - %{kilobytes}M 410 | ; - %{kilo}M 411 | ; - %{megabytes}M 412 | ; - %{mega}M 413 | ; %n: pool name 414 | ; %o: output header 415 | ; it must be associated with embraces to specify the name of the header: 416 | ; - %{Content-Type}o 417 | ; - %{X-Powered-By}o 418 | ; - %{Transfert-Encoding}o 419 | ; - .... 420 | ; %p: PID of the child that serviced the request 421 | ; %P: PID of the parent of the child that serviced the request 422 | ; %q: the query string 423 | ; %Q: the '?' character if query string exists 424 | ; %r: the request URI (without the query string, see %q and %Q) 425 | ; %R: remote IP address 426 | ; %s: status (response code) 427 | ; %t: server time the request was received 428 | ; it can accept a strftime(3) format: 429 | ; %d/%b/%Y:%H:%M:%S %z (default) 430 | ; %T: time the log has been written (the request has finished) 431 | ; it can accept a strftime(3) format: 432 | ; %d/%b/%Y:%H:%M:%S %z (default) 433 | ; %u: remote user 434 | ; 435 | ; Default: "%R - %u %t \"%m %r\" %s" 436 | ;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" 437 | 438 | ; The log file for slow requests 439 | ; Default Value: not set 440 | ; Note: slowlog is mandatory if request_slowlog_timeout is set 441 | ;slowlog = log/$pool.log.slow 442 | 443 | ; The timeout for serving a single request after which a PHP backtrace will be 444 | ; dumped to the 'slowlog' file. A value of '0s' means 'off'. 445 | ; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) 446 | ; Default Value: 0 447 | ;request_slowlog_timeout = 0 448 | 449 | ; The timeout for serving a single request after which the worker process will 450 | ; be killed. This option should be used when the 'max_execution_time' ini option 451 | ; does not stop script execution for some reason. A value of '0' means 'off'. 452 | ; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) 453 | ; Default Value: 0 454 | ;request_terminate_timeout = 0 455 | 456 | ; Set open file descriptor rlimit. 457 | ; Default Value: system defined value 458 | ;rlimit_files = 1024 459 | 460 | ; Set max core size rlimit. 461 | ; Possible Values: 'unlimited' or an integer greater or equal to 0 462 | ; Default Value: system defined value 463 | ;rlimit_core = 0 464 | 465 | ; Chroot to this directory at the start. This value must be defined as an 466 | ; absolute path. When this value is not set, chroot is not used. 467 | ; Note: you can prefix with '$prefix' to chroot to the pool prefix or one 468 | ; of its subdirectories. If the pool prefix is not set, the global prefix 469 | ; will be used instead. 470 | ; Note: chrooting is a great security feature and should be used whenever 471 | ; possible. However, all PHP paths will be relative to the chroot 472 | ; (error_log, sessions.save_path, ...). 473 | ; Default Value: not set 474 | ;chroot = 475 | 476 | ; Chdir to this directory at the start. 477 | ; Note: relative path can be used. 478 | ; Default Value: current directory or / when chroot 479 | ;chdir = /var/www 480 | 481 | ; Redirect worker stdout and stderr into main error log. If not set, stdout and 482 | ; stderr will be redirected to /dev/null according to FastCGI specs. 483 | ; Note: on highloaded environement, this can cause some delay in the page 484 | ; process time (several ms). 485 | ; Default Value: no 486 | catch_workers_output = yes 487 | 488 | ; Clear environment in FPM workers 489 | ; Prevents arbitrary environment variables from reaching FPM worker processes 490 | ; by clearing the environment in workers before env vars specified in this 491 | ; pool configuration are added. 492 | ; Setting to "no" will make all environment variables available to PHP code 493 | ; via getenv(), $_ENV and $_SERVER. 494 | ; Default Value: yes 495 | clear_env = no 496 | 497 | ; Limits the extensions of the main script FPM will allow to parse. This can 498 | ; prevent configuration mistakes on the web server side. You should only limit 499 | ; FPM to .php extensions to prevent malicious users to use other extensions to 500 | ; exectute php code. 501 | ; Note: set an empty value to allow all extensions. 502 | ; Default Value: .php 503 | ;security.limit_extensions = .php .php3 .php4 .php5 504 | 505 | ; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from 506 | ; the current environment. 507 | ; Default Value: clean env 508 | ;env[HOSTNAME] = $HOSTNAME 509 | ;env[PATH] = /usr/local/bin:/usr/bin:/bin 510 | ;env[TMP] = /tmp 511 | ;env[TMPDIR] = /tmp 512 | ;env[TEMP] = /tmp 513 | 514 | ; Additional php.ini defines, specific to this pool of workers. These settings 515 | ; overwrite the values previously defined in the php.ini. The directives are the 516 | ; same as the PHP SAPI: 517 | ; php_value/php_flag - you can set classic ini defines which can 518 | ; be overwritten from PHP call 'ini_set'. 519 | ; php_admin_value/php_admin_flag - these directives won't be overwritten by 520 | ; PHP call 'ini_set' 521 | ; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. 522 | 523 | ; Defining 'extension' will load the corresponding shared extension from 524 | ; extension_dir. Defining 'disable_functions' or 'disable_classes' will not 525 | ; overwrite previously defined php.ini values, but will append the new value 526 | ; instead. 527 | 528 | ; Note: path INI options can be relative and will be expanded with the prefix 529 | ; (pool, global or /usr) 530 | 531 | ; Default Value: nothing is defined by default except the values in php.ini and 532 | ; specified at startup with the -d argument 533 | ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com 534 | php_flag[display_errors] = off 535 | ;php_admin_value[error_log] = /var/log/fpm-php.www.log 536 | ;php_admin_flag[log_errors] = on 537 | ;php_admin_value[memory_limit] = 32M 538 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.2 2 | 3 | RUN apk add --update \ 4 | php-fpm \ 5 | php-mcrypt \ 6 | php-curl \ 7 | php-openssl \ 8 | php-phar \ 9 | php-ctype \ 10 | php-json \ 11 | curl \ 12 | git \ 13 | php-dom \ 14 | alpine-sdk \ 15 | php-dev \ 16 | autoconf \ 17 | openssl-dev \ 18 | php-pdo \ 19 | php-pdo_pgsql \ 20 | php-pdo_odbc \ 21 | php-pdo_mysql \ 22 | php-pdo_sqlite \ 23 | php-opcache && \ 24 | sed -i 's/\;date\.timezone\ \=/date\.timezone\ \=\ Europe\/Berlin/g' /etc/php/php.ini && \ 25 | curl -sS https://getcomposer.org/installer | php && \ 26 | mv composer.phar /usr/local/bin/composer && \ 27 | cd /tmp && git clone https://github.com/phpredis/phpredis.git && cd /tmp/phpredis && \ 28 | git checkout 2.2.7 && phpize && \ 29 | ./configure && \ 30 | make && \ 31 | make install && \ 32 | echo "extension=redis.so" >> /etc/php/conf.d/redis.ini && \ 33 | rm -rf /tmp/* && \ 34 | apk del --purge openssl-dev autoconf php-dev alpine-sdk && \ 35 | rm -rf /var/cache/apk/* 36 | COPY Docker/php-fpm.conf /etc/php/php-fpm.conf 37 | ADD . /var/www/ 38 | WORKDIR /var/www 39 | RUN chmod +x Docker/entrypoint.sh 40 | CMD ["Docker/entrypoint.sh"] 41 | EXPOSE 9000 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bogdan Popescu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: vendor/bin/heroku-php-nginx -C nginx.conf public/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dash Annotations Server 2 | 3 | Follow these instructions if you want to set up your own annotation server for [Dash](https://kapeli.com/dash). 4 | 5 | ## Installation 6 | 7 | * Install [Lumen](http://lumen.laravel.com/docs/installation) 8 | * Add a MySQL database called "annotations" 9 | * Clone this repo over your Lumen install 10 | * Rename the `.env.example` file to `.env` and edit it 11 | * Run `composer install` 12 | * Install Python and [Pygments](http://pygments.org/) (used for syntax highlighting) 13 | * Make sure `/bin/pygmentize` exists. If it doesn't, add a link between `/bin/pygmentize` to wherever you installed Pygments 14 | * Run `php artisan migrate` and type `Y` to confirm you want to do it in production 15 | * Open `http://{your_server}/users/logout` in your browser and check if you get a JSON response that says you're not logged in 16 | * Let Dash know about your server by running this command in Terminal: 17 | 18 | ```bash 19 | # Repeat on every Mac that will connect to your server: 20 | defaults write com.kapeli.dashdoc AnnotationsCustomServer "http(s)://{your_server}" 21 | 22 | # To go back to the default server: 23 | defaults delete com.kapeli.dashdoc AnnotationsCustomServer 24 | ``` 25 | 26 | * If you encounter any issues, [let me know](https://github.com/Kapeli/Dash-Annotations/issues/new)! 27 | 28 | ### Docker 29 | 30 | * Clone this repo 31 | * Build the image: `docker-compose build` 32 | * Generate your [GitHub Token](https://github.com/settings/tokens) and add it to `docker-compose.yml` 33 | * Set your `APP_KEY` in `docker-compose.yml` 34 | * Start the service: `docker-compose up -d` 35 | * Add `ProxyNginx.conf` to your nginx sites and edit your `server_name` 36 | * Open `http://dash.{your_server}/users/logout` in your browser and check if you get a JSON response that says you're not logged in 37 | * Let Dash know about your server by running this command in Terminal: 38 | 39 | ```bash 40 | # Repeat on every Mac that will connect to your server: 41 | defaults write com.kapeli.dashdoc AnnotationsCustomServer "http(s)://dash.{your_server}" 42 | 43 | # To go back to the default server: 44 | defaults delete com.kapeli.dashdoc AnnotationsCustomServer 45 | ``` 46 | 47 | * If you encounter any issues, [let me know](https://github.com/Kapeli/Dash-Annotations/issues/new)! 48 | 49 | 50 | ### Dokku 51 | > https://github.com/dokku-alt/dokku-alt 52 | 53 | * Clone this repo 54 | * Create remote for dokku: `git remote add dokku dokku@{your_server}:dash` 55 | * Create the app: `ssh -t dokku@{your_server} create dash` 56 | * Create the database: `ssh -t dokku@{your_server} mariadb:create dash-db` 57 | * Link database: `ssh -t dokku@{your_server} mariadb:link dash dash-db` 58 | * Get the database credentials: `ssh -t dokku@{your_server} mariadb:info dash dash-db` 59 | * Create environmental variables: 60 | ``` 61 | ssh -t dokku@{your_server} config:set dash \ 62 | APP_ENV=production \ 63 | APP_FALLBACK_LOCAL=en \ 64 | APP_KEY=SomeRandomKey! \ 65 | APP_LOCALE=en \ 66 | CACHE_DRIVER=file \ 67 | DB_CONNECTION=mysql \ 68 | DB_DATABASE=dash-db \ 69 | DB_HOST=mariadb \ 70 | DB_PASSWORD=YourPassword \ 71 | DB_USERNAME=dash \ 72 | QUEUE_DRIVER=file \ 73 | SESSION_DRIVER=file 74 | ``` 75 | 76 | * Push to dokku: `git push dokku dokku:master` 77 | * Get your server's URL: `ssh -t dokku@{your_server} url dash` 78 | * Open `http://dash.{your_server}/users/logout` in your browser and check if you get a JSON response that says you're not logged in 79 | * Let Dash know about your server by running this command in Terminal: 80 | 81 | ```bash 82 | # Repeat on every Mac that will connect to your server: 83 | defaults write com.kapeli.dashdoc AnnotationsCustomServer "http(s)://dash.{your_server}" 84 | 85 | # To go back to the default server: 86 | defaults delete com.kapeli.dashdoc AnnotationsCustomServer 87 | ``` 88 | 89 | * If you encounter any issues, [let me know](https://github.com/Kapeli/Dash-Annotations/issues/new)! 90 | -------------------------------------------------------------------------------- /app/Console/Commands/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kapeli/Dash-Annotations/9c575f555f75ef94776ccf07995a81b210fac280/app/Console/Commands/.gitkeep -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | {'status'}; 60 | if($app_store_response_status == 0 && strcmp(trim($app_store_response_map->receipt->bundle_id), "com.kapeli.dashdoc") == 0) 61 | { 62 | foreach($app_store_response_map->receipt->in_app as $in_app) 63 | { 64 | if(strcmp(trim($in_app->product_id), "FullVersion") == 0) 65 | { 66 | return true; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | return false; 73 | } 74 | } -------------------------------------------------------------------------------- /app/Http/Controllers/EntriesController.php: -------------------------------------------------------------------------------- 1 | find_in_db(); 22 | if($identifier) 23 | { 24 | $public_entries = NULL; 25 | $own_entries = NULL; 26 | $team_entries = NULL; 27 | 28 | $user = Auth::user(); 29 | if($user) 30 | { 31 | $team_ids = array(); 32 | foreach($user->teams()->get() as $team) 33 | { 34 | $team_ids[] = $team->id; 35 | } 36 | 37 | $public_entries = $identifier->entries()->where('public', '=', 1) 38 | ->where('user_id', '!=', $user->id) 39 | ->where('removed_from_public', '!=', 1) 40 | ->where('score', '>', $minimum_public_score)->get(); 41 | $own_entries = $identifier->entries()->where('user_id', '=', $user->id)->get(); 42 | if(smart_count($team_ids)) 43 | { 44 | $team_entries = $identifier->entries()->whereHas('teams', function($query) use ($user, $team_ids) 45 | { 46 | $query->where('user_id', '!=', $user->id) 47 | ->whereIn('team_id', $team_ids) 48 | ->where('removed_from_team', '=', 0); 49 | })->get(); 50 | if($team_entries && $public_entries) 51 | { 52 | $public_entries = $public_entries->filter(function($public_entry) use ($team_entries) 53 | { 54 | foreach($team_entries as $team_entry) 55 | { 56 | if($public_entry->id == $team_entry->id) 57 | { 58 | return false; 59 | } 60 | } 61 | return true; 62 | })->values(); 63 | } 64 | } 65 | } 66 | else 67 | { 68 | $public_entries = $identifier->entries()->where('public', '=', 1) 69 | ->where('score', '>', $minimum_public_score) 70 | ->where('removed_from_public', '!=', 1) 71 | ->get(); 72 | } 73 | 74 | $response = ["status" => "success"]; 75 | if(smart_count($public_entries)) 76 | { 77 | $response["public_entries"] = $public_entries; 78 | } 79 | if(smart_count($own_entries)) 80 | { 81 | $response["own_entries"] = $own_entries; 82 | } 83 | if(smart_count($team_entries)) 84 | { 85 | $response["team_entries"] = $team_entries; 86 | } 87 | return json_encode($response); 88 | } 89 | return json_encode(["status" => "success"]); 90 | } 91 | 92 | public function save() 93 | { 94 | if(Auth::check()) 95 | { 96 | $title = Request::input('title'); 97 | $body = Request::input('body'); 98 | $public = Request::input('public'); 99 | $type = Request::input('type'); 100 | $teams = Request::input('teams'); 101 | $license = Request::input('license'); 102 | $identifier_dict = Request::input('identifier'); 103 | $anchor = Request::input('anchor'); 104 | $entry_id = Request::input('entry_id'); 105 | $user = Auth::user(); 106 | 107 | if($title !== '' && $body !== '' && $type !== '' && !empty($identifier_dict) && $anchor !== '') 108 | { 109 | $db_license = NULL; 110 | if($public) 111 | { 112 | if(isset($_ENV['AUTH_LICENSES']) && $_ENV['AUTH_LICENSES']) 113 | { 114 | if(empty($license)) 115 | { 116 | return json_encode(['status' => 'error', 'message' => 'Only paid users can create public annotations']); 117 | } 118 | 119 | $json_license = json_encode($license); 120 | $db_license = License::where('license', '=', $json_license)->first(); 121 | if($db_license) 122 | { 123 | if($db_license->banned_from_public) 124 | { 125 | if(isset($license['is_beta']) && $license['is_beta']) 126 | { 127 | return json_encode(['status' => 'error', 'message' => "Beta users can't make public annotations"]); 128 | } 129 | else if(isset($license['is_promo']) && $license['is_promo']) 130 | { 131 | return json_encode(['status' => 'error', 'message' => $license['promo_name']." users can't make public annotations"]); 132 | } 133 | return json_encode(['status' => 'error', 'message' => 'You are banned from making public annotations']); 134 | } 135 | } 136 | else 137 | { 138 | if(isset($license['is_beta']) && $license['is_beta']) 139 | { 140 | // skip check for beta users 141 | } 142 | else if(isset($license['is_promo']) && $license['is_promo']) 143 | { 144 | // skip check for promo users 145 | } 146 | else if(isset($license['is_subscribed']) && $license['is_subscribed']) 147 | { 148 | // skip check for subscribed users 149 | } 150 | else if(isset($license['is_app_store']) && $license['is_app_store']) 151 | { 152 | if(!DashLicenseUtil::check_itunes_receipt($license)) 153 | { 154 | return json_encode(['status' => 'error', 'message' => 'Invalid license. Public annotation not allowed']); 155 | } 156 | } 157 | else 158 | { 159 | if(!DashLicenseUtil::check_license($license)) 160 | { 161 | return json_encode(['status' => 'error', 'message' => 'Invalid license. Public annotation not allowed']); 162 | } 163 | } 164 | 165 | $db_license = new License; 166 | $db_license->license = $json_license; 167 | $db_license->save(); 168 | } 169 | } 170 | } 171 | 172 | $identifier = Identifier::IdentifierFromDictionary($identifier_dict); 173 | $db_identifier = $identifier->find_in_db(); 174 | if(!$db_identifier) 175 | { 176 | $identifier->save(); 177 | $db_identifier = $identifier; 178 | } 179 | 180 | if($public && $db_identifier->banned_from_public) 181 | { 182 | return json_encode(['status' => 'error', 'message' => 'Public annotations are not allowed on this page']); 183 | } 184 | 185 | $entry = ($entry_id) ? Entry::where('id', '=', $entry_id)->first() : new Entry; 186 | if($entry_id && (!$entry || $entry->user_id != $user->id)) 187 | { 188 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 189 | } 190 | $entry->title = $title; 191 | $entry->body = $body; 192 | 193 | try { 194 | $body = MarkdownExtra::defaultTransform($body); 195 | } catch (\RuntimeException $e) { 196 | $message = $e->getMessage(); 197 | $start = strpos($message, 'no lexer for alias \''); 198 | if($start !== FALSE) 199 | { 200 | $start += 20; 201 | $end = strpos($message, '\'', $start); 202 | if($end !== FALSE) 203 | { 204 | $lexer = substr($message, $start, $end-$start); 205 | return json_encode(['status' => 'error', 'message' => 'Unknown syntax highlighting: '.$lexer]); 206 | } 207 | } 208 | throw $e; 209 | } 210 | $html_safe = new HTML_Safe(); 211 | $html_safe->protocolFiltering = 'black'; 212 | $body = $html_safe->parse($body); 213 | $body = str_replace('#dashInternal', '#', $body); 214 | $entry->body_rendered = $body; 215 | 216 | $entry->public = $public; 217 | $entry->type = $type; 218 | $entry->anchor = $anchor; 219 | $entry->user_id = $user->id; 220 | $entry->identifier_id = $db_identifier->id; 221 | if($db_license) 222 | { 223 | $entry->license_id = $db_license->id; 224 | } 225 | if(!$entry_id) 226 | { 227 | $entry->score = 1; 228 | } 229 | $entry->save(); 230 | if(!$entry_id) 231 | { 232 | $vote = new Vote; 233 | $vote->type = 1; 234 | $vote->user_id = $user->id; 235 | $vote->entry_id = $entry->id; 236 | $vote->save(); 237 | } 238 | $db_teams = $entry->teams(); 239 | $already_assigned = array(); 240 | foreach($db_teams->get() as $team) 241 | { 242 | if(!in_arrayi($team->name, $teams)) 243 | { 244 | $db_teams->detach($team->id); 245 | } 246 | else 247 | { 248 | $already_assigned[] = $team->name; 249 | } 250 | } 251 | foreach($teams as $team) 252 | { 253 | if(!in_arrayi($team, $already_assigned)) 254 | { 255 | $db_team = Team::where('name', '=', $team)->first(); 256 | if($db_team && $db_team->users()->where('user_id', '=', $user->id)->first()) 257 | { 258 | $db_team->entries()->attach($entry->id); 259 | } 260 | } 261 | } 262 | return json_encode(['status' => 'success', 'entry' => $entry]); 263 | } 264 | return json_encode(['status' => 'error', 'message' => 'Oops. Unknown error']); 265 | } 266 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 267 | } 268 | 269 | public function delete() 270 | { 271 | if(Auth::check()) 272 | { 273 | $entry_id = Request::input('entry_id'); 274 | $entry = Entry::where('id', '=', $entry_id)->first(); 275 | $user = Auth::user(); 276 | if($entry && $entry->user_id == $user->id) 277 | { 278 | $entry->teams()->detach(); 279 | Vote::where('entry_id', '=', $entry_id)->delete(); 280 | $entry->delete(); 281 | return json_encode(['status' => 'success']); 282 | } 283 | } 284 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 285 | } 286 | 287 | public function remove_from_public() 288 | { 289 | if(Auth::check()) 290 | { 291 | $user = Auth::user(); 292 | if($user->moderator) 293 | { 294 | $entry_id = Request::input('entry_id'); 295 | $entry = Entry::where('id', '=', $entry_id)->first(); 296 | if($entry) 297 | { 298 | $entry->removed_from_public = true; 299 | $entry->save(); 300 | return json_encode(['status' => 'success']); 301 | } 302 | } 303 | } 304 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 305 | } 306 | 307 | public function remove_from_teams() 308 | { 309 | if(Auth::check()) 310 | { 311 | $user = Auth::user(); 312 | $entry_id = Request::input('entry_id'); 313 | $entry = Entry::where('id', '=', $entry_id)->first(); 314 | if($entry) 315 | { 316 | $entry_teams = $entry->teams()->get(); 317 | foreach($entry_teams as $team) 318 | { 319 | $check = $team->users()->where('user_id', '=', $user->id)->first(); 320 | if($check) 321 | { 322 | $role = $check->pivot->role; 323 | if($role && ($role == 'owner' || $role == 'moderator')) 324 | { 325 | $team->pivot->removed_from_team = true; 326 | $team->pivot->save(); 327 | } 328 | } 329 | } 330 | return json_encode(['status' => 'success']); 331 | } 332 | } 333 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 334 | } 335 | 336 | public function vote() 337 | { 338 | if(Auth::check()) 339 | { 340 | $entry_id = Request::input('entry_id'); 341 | $vote_type = Request::input('vote_type'); 342 | if($vote_type > 1 || $vote_type < -1) 343 | { 344 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 345 | } 346 | $entry = Entry::where('id', '=', $entry_id)->first(); 347 | if($entry) 348 | { 349 | $entry_teams = $entry->teams()->get(); 350 | $user = Auth::user(); 351 | if(!$entry->public && $entry->user_id != $user->id) 352 | { 353 | $found = false; 354 | foreach($entry_teams as $team) 355 | { 356 | $check = $team->users()->where('user_id', '=', $user->id)->first(); 357 | if($check) 358 | { 359 | $found = true; 360 | break; 361 | } 362 | } 363 | if(!$found) 364 | { 365 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 366 | } 367 | } 368 | $vote = Vote::where('entry_id', '=', $entry_id)->where('user_id', '=', $user->id)->first(); 369 | if($vote) 370 | { 371 | $entry->score -= $vote->type; 372 | if($vote_type == 0) 373 | { 374 | $entry->save(); 375 | $vote->delete(); 376 | return json_encode(['status' => 'success']); 377 | } 378 | } 379 | else if($vote_type == 0) 380 | { 381 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 382 | } 383 | $vote = ($vote) ? $vote : new Vote; 384 | $vote->entry_id = $entry_id; 385 | $vote->user_id = $user->id; 386 | $vote->type = $vote_type; 387 | $entry->score += $vote_type; 388 | $entry->save(); 389 | $vote->save(); 390 | return json_encode(['status' => 'success']); 391 | } 392 | } 393 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 394 | } 395 | 396 | public function get() 397 | { 398 | $entry_id = Request::input('entry_id'); 399 | $entry = Entry::where('id', '=', $entry_id)->first(); 400 | if($entry) 401 | { 402 | $my_teams = array(); 403 | $global_moderator = false; 404 | $team_moderator = false; 405 | $entry_user = $entry->user()->first(); 406 | $user = NULL; 407 | if(!Auth::check()) 408 | { 409 | if(!$entry->public) 410 | { 411 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 412 | } 413 | } 414 | else 415 | { 416 | $entry_teams = $entry->teams()->get(); 417 | $user = Auth::user(); 418 | foreach($entry_teams as $team) 419 | { 420 | if($entry->user_id == $user->id || !$team->pivot->removed_from_team) 421 | { 422 | $check = $team->users()->where('user_id', '=', $user->id)->first(); 423 | if($check) 424 | { 425 | $role = $check->pivot->role; 426 | $my_teams[] = ["name" => $team->name, "role" => $role]; 427 | if($role && ($role == 'owner' || $role == 'moderator')) 428 | { 429 | $team_moderator = true; 430 | } 431 | } 432 | } 433 | } 434 | if(!$entry->public && !smart_count($my_teams) && $entry->user_id != $user->id) 435 | { 436 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 437 | } 438 | if($entry->public && $user->moderator) 439 | { 440 | $global_moderator = true; 441 | } 442 | } 443 | $body_rendered = $entry->body_rendered; 444 | $body = ' 445 | 446 | 447 | 448 | ".$entry->title." 449 | 450 | 451 | 452 | 453 | 726 | 727 | 728 |
'; 729 | 730 | if($entry->public || smart_count($my_teams)) 731 | { 732 | $voted_up = ""; 733 | $voted_down = ""; 734 | if($user) 735 | { 736 | $vote = Vote::where('user_id', '=', $user->id)->where('entry_id', '=', $entry->id)->first(); 737 | if($vote) 738 | { 739 | $voted_up = ($vote->type == 1) ? "voted" : ""; 740 | $voted_down = ($vote->type == -1) ? "voted" : ""; 741 | } 742 | } 743 | $score = ($entry->score > 999) ? 999 : $entry->score; 744 | $score = ($score < -999) ? -999 : $score; 745 | $body .= ' 746 |
747 |
748 |
'.$score.'
749 |
750 |
'; 751 | } 752 | $body .= ' 753 |

'.htmlentities($entry->title, ENT_QUOTES).'

754 |

'; 755 | 756 | $body .= ($entry->public && !($entry->removed_from_public && $global_moderator)) ? "Public annotation " : @"Private annotation "; 757 | $body .= 'by '.htmlentities($entry_user->username, ENT_QUOTES).''; 758 | $team_string = ""; 759 | $i = 0; 760 | foreach($my_teams as $team) 761 | { 762 | ++$i; 763 | if(strlen($team_string)) 764 | { 765 | $team_string .= (smart_count($my_teams) == $i) ? " and " : ", "; 766 | } 767 | $team_string .= ''.htmlentities($team['name'], ENT_QUOTES).''; 768 | } 769 | if(strlen($team_string)) 770 | { 771 | $body .= ' in '.$team_string.''; 772 | } 773 | $body .= ' '; 774 | if($user && $user->id == $entry->user_id) 775 | { 776 | $body .= ' Edit'; 777 | $body .= ' Delete'; 778 | } 779 | else 780 | { 781 | if($global_moderator && $entry->public && !$entry->removed_from_public) 782 | { 783 | $body .= ' Remove From Public'; 784 | } 785 | if($team_moderator) 786 | { 787 | $body .= ' Remove From Team'; 788 | if(smart_count($my_teams) > 1) 789 | { 790 | $body .= 's'; 791 | } 792 | $body .= ''; 793 | } 794 | } 795 | $body .= '

796 |
'.$body_rendered.'
797 |
798 | 802 | 803 | '; 804 | return json_encode(["status" => "success", "body" => $entry->body, "body_rendered" => $body, "teams" => $my_teams, "global_moderator" => $global_moderator]); 805 | } 806 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 807 | } 808 | } 809 | 810 | function smart_count($array) 811 | { 812 | if($array === null || !is_countable($array)) 813 | { 814 | return 0; 815 | } 816 | return count($array); 817 | } 818 | 819 | function in_arrayi($needle, $haystack) 820 | { 821 | return in_array(strtolower($needle), array_map('strtolower', $haystack)); 822 | } 823 | 824 | function autolinker_js() 825 | { 826 | 827 | // Copy paste from https://github.com/gregjacobs/Autolinker.js/blob/master/dist/Autolinker.min.js, just need to replace \ with \\ for some reason 828 | 829 | return << 835 | * MIT License 836 | * 837 | * https://github.com/gregjacobs/Autolinker.js 838 | */ 839 | !function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.Autolinker=t()}(this,function(){var e=function(t){t=t||{},this.version=e.version,this.urls=this.normalizeUrlsCfg(t.urls),this.email="boolean"!=typeof t.email||t.email,this.phone="boolean"!=typeof t.phone||t.phone,this.hashtag=t.hashtag||!1,this.mention=t.mention||!1,this.newWindow="boolean"!=typeof t.newWindow||t.newWindow,this.stripPrefix=this.normalizeStripPrefixCfg(t.stripPrefix),this.stripTrailingSlash="boolean"!=typeof t.stripTrailingSlash||t.stripTrailingSlash,this.decodePercentEncoding="boolean"!=typeof t.decodePercentEncoding||t.decodePercentEncoding;var r=this.mention;if(r!==!1&&"twitter"!==r&&"instagram"!==r)throw new Error("invalid `mention` cfg - see docs");var n=this.hashtag;if(n!==!1&&"twitter"!==n&&"facebook"!==n&&"instagram"!==n)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(t.truncate),this.className=t.className||"",this.replaceFn=t.replaceFn||null,this.context=t.context||this,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return e.link=function(t,r){var n=new e(r);return n.link(t)},e.parse=function(t,r){var n=new e(r);return n.parse(t)},e.version="1.7.1",e.prototype={constructor:e,normalizeUrlsCfg:function(e){return null==e&&(e=!0),"boolean"==typeof e?{schemeMatches:e,wwwMatches:e,tldMatches:e}:{schemeMatches:"boolean"!=typeof e.schemeMatches||e.schemeMatches,wwwMatches:"boolean"!=typeof e.wwwMatches||e.wwwMatches,tldMatches:"boolean"!=typeof e.tldMatches||e.tldMatches}},normalizeStripPrefixCfg:function(e){return null==e&&(e=!0),"boolean"==typeof e?{scheme:e,www:e}:{scheme:"boolean"!=typeof e.scheme||e.scheme,www:"boolean"!=typeof e.www||e.www}},normalizeTruncateCfg:function(t){return"number"==typeof t?{length:t,location:"end"}:e.Util.defaults(t||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(e){for(var t=this.getHtmlParser(),r=t.parse(e),n=0,a=[],i=0,s=r.length;ia?t:t+1;e.splice(s,1);continue}e[t+1].getOffset()t&&(null==r?(r="…",n=3):n=r.length,e=e.substring(0,t-n)+r),e},indexOf:function(e,t){if(Array.prototype.indexOf)return e.indexOf(t);for(var r=0,n=e.length;r=0;r--)t(e[r])===!0&&e.splice(r,1)},splitAndCapture:function(e,t){for(var r,n=[],a=0;r=t.exec(e);)n.push(e.substring(a,r.index)),n.push(r[0]),a=r.index+r[0].length;return n.push(e.substring(a)),n},trim:function(e){return e.replace(this.trimRegex,"")}},e.HtmlTag=e.Util.extend(Object,{whitespaceRegex:/\\s+/,constructor:function(t){e.Util.assign(this,t),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(e){return this.tagName=e,this},getTagName:function(){return this.tagName||""},setAttr:function(e,t){var r=this.getAttrs();return r[e]=t,this},getAttr:function(e){return this.getAttrs()[e]},setAttrs:function(t){var r=this.getAttrs();return e.Util.assign(r,t),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(e){return this.setAttr("class",e)},addClass:function(t){for(var r,n=this.getClass(),a=this.whitespaceRegex,i=e.Util.indexOf,s=n?n.split(a):[],o=t.split(a);r=o.shift();)i(s,r)===-1&&s.push(r);return this.getAttrs()["class"]=s.join(" "),this},removeClass:function(t){for(var r,n=this.getClass(),a=this.whitespaceRegex,i=e.Util.indexOf,s=n?n.split(a):[],o=t.split(a);s.length&&(r=o.shift());){var c=i(s,r);c!==-1&&s.splice(c,1)}return this.getAttrs()["class"]=s.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(e){return(" "+this.getClass()+" ").indexOf(" "+e+" ")!==-1},setInnerHtml:function(e){return this.innerHtml=e,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var e=this.getTagName(),t=this.buildAttrsStr();return t=t?" "+t:"",["<",e,t,">",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var e=this.getAttrs(),t=[];for(var r in e)e.hasOwnProperty(r)&&t.push(r+'="'+e[r]+'"');return t.join(" ")}}),e.RegexLib=function(){var e="A-Za-z\\\\xAA\\\\xB5\\\\xBA\\\\xC0-\\\\xD6\\\\xD8-\\\\xF6\\\\xF8-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",t="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",r=e+t,n="(?:["+t+"]{1,3}\\\\.){3}["+t+"]{1,3}",a="["+r+"](?:["+r+"\\\\-]{0,61}["+r+"])?",i=function(e){return"(?=("+a+"))\\\\"+e},s=function(e){return"(?:"+i(e)+"(?:\\\\."+i(e+1)+"){0,126}|"+n+")"};return{alphaNumericCharsStr:r,alphaCharsStr:e,getDomainNameStr:s}}(),e.AnchorTagBuilder=e.Util.extend(Object,{constructor:function(e){e=e||{},this.newWindow=e.newWindow,this.truncate=e.truncate,this.className=e.className},build:function(t){return new e.HtmlTag({tagName:"a",attrs:this.createAttrs(t),innerHtml:this.processAnchorText(t.getAnchorText())})},createAttrs:function(e){var t={href:e.getAnchorHref()},r=this.createCssClass(e);return r&&(t["class"]=r),this.newWindow&&(t.target="_blank",t.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length\\/=\\x00-\\x1F\\x7F]+/,n=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\\s]+)/,a="(?:\\\\s*?=\\\\s*?"+n.source+")?",i=function(e){return"(?=("+r.source+"))\\\\"+e+a};return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\\\s+","(?:",i(2),"|",n.source+")",")*",">",")","|","(?:","<(/)?","(?:",e.source,"|","(?:","("+t.source+")","\\\\s*/?",")","|","(?:","("+t.source+")","\\\\s+","(?:","(?:\\\\s+|\\\\b)",i(7),")*","\\\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(e){for(var t,r,n=this.htmlRegex,a=0,i=[];null!==(t=n.exec(e));){var s=t[0],o=t[4],c=t[1]||t[5]||t[6],h=!!t[3],l=t.index,u=e.substring(a,l);u&&(r=this.parseTextAndEntityNodes(a,u),i.push.apply(i,r)),o?i.push(this.createCommentNode(l,s,o)):i.push(this.createElementNode(l,s,c,h)),a=l+s.length}if(a@\\\\[\\\\]',a=t+r,i=a+n,s=new RegExp("(?:["+a+"](?:["+a+']|\\\\.(?!\\\\.|@))*|\\\\"['+i+'.]+\\\\")@'),o=e.RegexLib.getDomainNameStr,c=e.tldRegex;return new RegExp([s.source,o(1),"\\\\.",c.source].join(""),"gi")}(),parseMatches:function(t){for(var r,n=this.matcherRegex,a=this.tagBuilder,i=[];null!==(r=n.exec(t));){var s=r[0];i.push(new e.match.Email({tagBuilder:a,matchedText:s,offset:r.index,email:s}))}return i}}),e.matcher.Hashtag=e.Util.extend(e.matcher.Matcher,{matcherRegex:new RegExp("#[_"+e.RegexLib.alphaNumericCharsStr+"]{1,139}","g"),nonWordCharRegex:new RegExp("[^"+e.RegexLib.alphaNumericCharsStr+"]"),constructor:function(t){e.matcher.Matcher.prototype.constructor.call(this,t),this.serviceName=t.serviceName},parseMatches:function(t){for(var r,n=this.matcherRegex,a=this.nonWordCharRegex,i=this.serviceName,s=this.tagBuilder,o=[];null!==(r=n.exec(t));){var c=r.index,h=t.charAt(c-1);if(0===c||a.test(h)){var l=r[0],u=r[0].slice(1);o.push(new e.match.Hashtag({tagBuilder:s,matchedText:l,offset:c,serviceName:i,hashtag:u}))}}return o}}),e.matcher.Phone=e.Util.extend(e.matcher.Matcher,{matcherRegex:/(?:(?:(?:(\\+)?\\d{1,3}[-\\040.]?)?\\(?\\d{3}\\)?[-\\040.]?\\d{3}[-\\040.]?\\d{4})|(?:(\\+)(?:9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\\040.]?(?:\\d[-\\040.]?){6,12}\\d+))([,;]+[0-9]+#?)*/g,parseMatches:function(t){for(var r,n=this.matcherRegex,a=this.tagBuilder,i=[];null!==(r=n.exec(t));){var s=r[0],o=s.replace(/[^0-9,;#]/g,""),c=!(!r[1]&&!r[2]),h=0==r.index?"":t.substr(r.index-1,1),l=t.substr(r.index+s.length,1),u=!h.match(/\\d/)&&!l.match(/\\d/);this.testMatch(r[3])&&this.testMatch(s)&&u&&i.push(new e.match.Phone({tagBuilder:a,matchedText:s,offset:r.index,number:o,plusSign:c}))}return i},testMatch:function(e){return/\\D/.test(e)}}),e.matcher.Mention=e.Util.extend(e.matcher.Matcher,{matcherRegexes:{twitter:new RegExp("@[_"+e.RegexLib.alphaNumericCharsStr+"]{1,20}","g"),instagram:new RegExp("@[_."+e.RegexLib.alphaNumericCharsStr+"]{1,50}","g")},nonWordCharRegex:new RegExp("[^"+e.RegexLib.alphaNumericCharsStr+"]"),constructor:function(t){e.matcher.Matcher.prototype.constructor.call(this,t),this.serviceName=t.serviceName},parseMatches:function(t){var r,n=this.matcherRegexes[this.serviceName],a=this.nonWordCharRegex,i=this.serviceName,s=this.tagBuilder,o=[];if(!n)return o;for(;null!==(r=n.exec(t));){var c=r.index,h=t.charAt(c-1);if(0===c||a.test(h)){var l=r[0].replace(/\\.+$/g,""),u=l.slice(1);o.push(new e.match.Mention({tagBuilder:s,matchedText:l,offset:c,serviceName:i,mention:u}))}}return o}}),e.matcher.Url=e.Util.extend(e.matcher.Matcher,{matcherRegex:function(){var t=/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\\/\\/)(?!\\d+\\/?)(?:\\/\\/)?)/,r=/(?:www\\.)/,n=e.RegexLib.getDomainNameStr,a=e.tldRegex,i=e.RegexLib.alphaNumericCharsStr,s=new RegExp("[/?#](?:["+i+"\\\\-+&@#/%=~_()|'$*\\\\[\\\\]?!:,.;✓]*["+i+"\\\\-+&@#/%=~_()|'$*\\\\[\\\\]✓])?");return new RegExp(["(?:","(",t.source,n(2),")","|","(","(//)?",r.source,n(6),")","|","(","(//)?",n(10)+"\\\\.",a.source,"(?![-"+i+"])",")",")","(?::[0-9]+)?","(?:"+s.source+")?"].join(""),"gi")}(),wordCharRegExp:new RegExp("["+e.RegexLib.alphaNumericCharsStr+"]"),openParensRe:/\\(/g,closeParensRe:/\\)/g,constructor:function(t){e.matcher.Matcher.prototype.constructor.call(this,t),this.stripPrefix=t.stripPrefix,this.stripTrailingSlash=t.stripTrailingSlash,this.decodePercentEncoding=t.decodePercentEncoding},parseMatches:function(t){for(var r,n=this.matcherRegex,a=this.stripPrefix,i=this.stripTrailingSlash,s=this.decodePercentEncoding,o=this.tagBuilder,c=[];null!==(r=n.exec(t));){var h=r[0],l=r[1],u=r[4],g=r[5],m=r[9],f=r.index,d=g||m,p=t.charAt(f-1);if(e.matcher.UrlMatchValidator.isValid(h,l)&&!(f>0&&"@"===p||f>0&&d&&this.wordCharRegExp.test(p))){if(/\\?$/.test(h)&&(h=h.substr(0,h.length-1)),this.matchHasUnbalancedClosingParen(h))h=h.substr(0,h.length-1);else{var x=this.matchHasInvalidCharAfterTld(h,l);x>-1&&(h=h.substr(0,x))}var b=l?"scheme":u?"www":"tld",v=!!l;c.push(new e.match.Url({tagBuilder:o,matchedText:h,offset:f,urlMatchType:b,url:h,protocolUrlMatch:v,protocolRelativeMatch:!!d,stripPrefix:a,stripTrailingSlash:i,decodePercentEncoding:s}))}}return c},matchHasUnbalancedClosingParen:function(e){var t=e.charAt(e.length-1);if(")"===t){var r=e.match(this.openParensRe),n=e.match(this.closeParensRe),a=r&&r.length||0,i=n&&n.length||0; 840 | if(a-1},isValidUriScheme:function(e){var t=e.match(this.uriSchemeRegex)[0].toLowerCase();return"javascript:"!==t&&"vbscript:"!==t},urlMatchDoesNotHaveProtocolOrDot:function(e,t){return!(!e||t&&this.hasFullProtocolRegex.test(t)||e.indexOf(".")!==-1)},urlMatchDoesNotHaveAtLeastOneWordChar:function(e,t){return!(!e||!t)&&!this.hasWordCharAfterProtocolRegex.test(e)}},e.truncate.TruncateEnd=function(t,r,n){return e.Util.ellipsis(t,r,n)},e.truncate.TruncateMiddle=function(e,t,r){if(e.length<=t)return e;var n,a;null==r?(r="…",n=8,a=3):(n=r.length,a=r.length);var i=t-a,s="";return i>0&&(s=e.substr(-1*Math.floor(i/2))),(e.substr(0,Math.ceil(i/2))+r+s).substr(0,i+n)},e.truncate.TruncateSmart=function(e,t,r){var n,a;null==r?(r="…",a=3,n=8):(a=r.length,n=r.length);var i=function(e){var t={},r=e,n=r.match(/^([a-z]+):\\/\\//i);return n&&(t.scheme=n[1],r=r.substr(n[0].length)),n=r.match(/^(.*?)(?=(\\?|#|\\/|$))/i),n&&(t.host=n[1],r=r.substr(n[0].length)),n=r.match(/^\\/(.*?)(?=(\\?|#|$))/i),n&&(t.path=n[1],r=r.substr(n[0].length)),n=r.match(/^\\?(.*?)(?=(#|$))/i),n&&(t.query=n[1],r=r.substr(n[0].length)),n=r.match(/^#(.*?)$/i),n&&(t.fragment=n[1]),t},s=function(e){var t="";return e.scheme&&e.host&&(t+=e.scheme+"://"),e.host&&(t+=e.host),e.path&&(t+="/"+e.path),e.query&&(t+="?"+e.query),e.fragment&&(t+="#"+e.fragment),t},o=function(e,t){var n=t/2,a=Math.ceil(n),i=-1*Math.floor(n),s="";return i<0&&(s=e.substr(i)),e.substr(0,a)+r+s};if(e.length<=t)return e;var c=t-a,h=i(e);if(h.query){var l=h.query.match(/^(.*?)(?=(\\?|\\#))(.*?)$/i);l&&(h.query=h.query.substr(0,l[1].length),e=s(h))}if(e.length<=t)return e;if(h.host&&(h.host=h.host.replace(/^www\\./,""),e=s(h)),e.length<=t)return e;var u="";if(h.host&&(u+=h.host),u.length>=c)return h.host.length==t?(h.host.substr(0,t-a)+r).substr(0,c+n):o(u,c).substr(0,c+n);var g="";if(h.path&&(g+="/"+h.path),h.query&&(g+="?"+h.query),g){if((u+g).length>=c){if((u+g).length==t)return(u+g).substr(0,t);var m=c-u.length;return(u+o(g,m)).substr(0,c+n)}u+=g}if(h.fragment){var f="#"+h.fragment;if((u+f).length>=c){if((u+f).length==t)return(u+f).substr(0,t);var d=c-u.length;return(u+o(f,d)).substr(0,c+n)}u+=f}if(h.scheme&&h.host){var p=h.scheme+"://";if((u+p).length0&&(x=u.substr(-1*Math.floor(c/2))),(u.substr(0,Math.ceil(c/2))+r+x).substr(0,c+n)},e}); 841 | END; 842 | } -------------------------------------------------------------------------------- /app/Http/Controllers/ForgotController.php: -------------------------------------------------------------------------------- 1 | $email); 18 | $response = Password::sendResetLink($credentials); 19 | switch ($response) 20 | { 21 | case PasswordBroker::RESET_LINK_SENT: 22 | return json_encode(['status' => 'success']); 23 | 24 | case PasswordBroker::INVALID_USER: 25 | return json_encode(['status' => 'error', 'message' => 'No user has this email']); 26 | } 27 | } 28 | return json_encode(['status' => 'error', 'message' => 'Invalid email']); 29 | } 30 | 31 | public function reset() 32 | { 33 | 34 | $email = Request::input('email'); 35 | if(!empty($email)) 36 | { 37 | $user = User::where('email', '=', $email)->first(); 38 | if($user == NULL) 39 | { 40 | return json_encode(['status' => 'error', 'message' => 'Invalid reset token']); 41 | } 42 | else 43 | { 44 | $username = $user->username; 45 | $credentials = Request::only( 46 | 'email', 'password', 'token' 47 | ); 48 | $credentials['password_confirmation'] = $credentials['password']; 49 | $credentials['username'] = $username; 50 | $result = Password::reset($credentials, function($user, $password) 51 | { 52 | $user->password = Hash::make($password); 53 | $user->save(); 54 | }); 55 | switch ($result) 56 | { 57 | case PasswordBroker::PASSWORD_RESET: 58 | return json_encode(['status' => 'success', 'username' => $username]); 59 | } 60 | } 61 | } 62 | return json_encode(['status' => 'error', 'message' => 'Invalid reset token']); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Http/Controllers/TeamsController.php: -------------------------------------------------------------------------------- 1 | ["required", "unique:teams,name"], 18 | ]); 19 | 20 | if($validator->passes()) 21 | { 22 | $name = Request::input('name'); 23 | $team = new Team; 24 | $team->name = $name; 25 | Auth::user()->teams()->save($team, array('role' => 'owner')); 26 | return json_encode(['status' => 'success']); 27 | } 28 | else 29 | { 30 | return json_encode(['status' => 'error', 'message' => 'Team name already taken']); 31 | } 32 | return json_encode(['status' => 'error']); 33 | } 34 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 35 | } 36 | 37 | public function leave() 38 | { 39 | if(Auth::check()) 40 | { 41 | $name = Request::input('name'); 42 | if($name !== '') 43 | { 44 | $team = Team::where('name', '=', $name)->first(); 45 | if($team) 46 | { 47 | $user = Auth::user(); 48 | $this->remove_posts($user, $team); 49 | $team->users()->detach($user->id); 50 | if($team->users()->count() == 0) 51 | { 52 | $team->delete(); 53 | } 54 | return json_encode(['status' => 'success']); 55 | } 56 | } 57 | } 58 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 59 | } 60 | 61 | public function remove_member() 62 | { 63 | if(Auth::check()) 64 | { 65 | $name = Request::input('name'); 66 | $toRemove = Request::input('username'); 67 | if($name !== '') 68 | { 69 | $team = Team::where('name', '=', $name)->first(); 70 | if($team && $team->owner() && $team->owner()->id == Auth::user()->id) 71 | { 72 | $user = User::where('username', '=', $toRemove)->first(); 73 | if($user) 74 | { 75 | $this->remove_posts($user, $team); 76 | $team->users()->detach($user->id); 77 | } 78 | return json_encode(['status' => 'success']); 79 | } 80 | } 81 | } 82 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 83 | } 84 | 85 | public function remove_posts($user, $team) 86 | { 87 | foreach($team->entries()->where('user_id', '=', $user->id)->get() as $entry) 88 | { 89 | $team->entries()->detach($entry->id); 90 | } 91 | } 92 | 93 | public function join() 94 | { 95 | if(Auth::check()) 96 | { 97 | $name = Request::input('name'); 98 | $access_key = Request::input('access_key'); 99 | if($name !== '') 100 | { 101 | $team = Team::where('name', '=', $name)->first(); 102 | if($team) 103 | { 104 | if(($team->access_key == '' && $access_key == '') || Hash::check($access_key, $team->access_key)) 105 | { 106 | if($team->users()->where('user_id', '=', Auth::user()->id)->first()) 107 | { 108 | return json_encode(['status' => 'error', 'message' => 'You are already a member of this team']); 109 | } 110 | $team->users()->attach(Auth::user()->id, array('role' => 'member')); 111 | return json_encode(['status' => 'success']); 112 | } 113 | return json_encode(['status' => 'error', 'message' => 'Invalid access key']); 114 | } 115 | return json_encode(['status' => 'error', 'message' => 'Team does not exist']); 116 | } 117 | } 118 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 119 | } 120 | 121 | public function set_access_key() 122 | { 123 | if(Auth::check()) 124 | { 125 | $name = Request::input('name'); 126 | $key = Request::input('access_key'); 127 | if($name !== '') 128 | { 129 | $team = Team::where('name', '=', $name)->first(); 130 | if($team && $team->owner() && $team->owner()->id == Auth::user()->id) 131 | { 132 | if($key !== '') 133 | { 134 | $key = Hash::make($key); 135 | } 136 | $team->access_key = $key; 137 | $team->save(); 138 | return json_encode(['status' => 'success']); 139 | } 140 | return json_encode(['status' => 'error', 'message' => 'Unknown error']); 141 | } 142 | } 143 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 144 | } 145 | 146 | public function set_role() 147 | { 148 | if(Auth::check()) 149 | { 150 | $name = Request::input('name'); 151 | $username = Request::input('username'); 152 | $role = Request::input('role'); 153 | if($name !== '' && $role !== '' && $username !== '') 154 | { 155 | $team = Team::where('name', '=', $name)->first(); 156 | if($team && $team->owner() && $team->owner()->id == Auth::user()->id) 157 | { 158 | $target_user = $team->users()->where('username', '=', $username)->first(); 159 | if($target_user) 160 | { 161 | $team->users()->updateExistingPivot($target_user->id, ['role' => $role]); 162 | return json_encode(['status' => 'success']); 163 | } 164 | } 165 | return json_encode(['status' => 'error', 'message' => 'Unknown error']); 166 | } 167 | } 168 | return json_encode(['status' => 'error', 'message' => 'Unknown error']); 169 | } 170 | 171 | public function list_teams() 172 | { 173 | if(Auth::check()) 174 | { 175 | $teams = array(); 176 | foreach(Auth::user()->teams()->get() as $team) 177 | { 178 | $teams[] = ['name' => $team->name, 'role' => $team->pivot->role]; 179 | } 180 | return json_encode(['status' => 'success', 'teams' => $teams]); 181 | } 182 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 183 | } 184 | 185 | public function list_members() 186 | { 187 | if(Auth::check()) 188 | { 189 | $name = Request::input('name'); 190 | if($name !== '') 191 | { 192 | $members = array(); 193 | $team = Team::where('name', '=', $name)->first(); 194 | if($team && $team->owner() && $team->owner()->id == Auth::user()->id) 195 | { 196 | foreach($team->users()->get() as $user) 197 | { 198 | $members[] = ['name' => $user->username, 'role' => $user->pivot->role]; 199 | } 200 | return json_encode(['status' => 'success', 'members' => $members, 'has_access_key' => ($team->access_key) ? true : false]); 201 | } 202 | } 203 | } 204 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/Http/Controllers/UsersController.php: -------------------------------------------------------------------------------- 1 | ["required", "unique:users,username"], 15 | "password" => "required" 16 | ]); 17 | 18 | if($validator->passes()) 19 | { 20 | $username = Request::input('username'); 21 | $password = Request::input('password'); 22 | $user = new User; 23 | $user->username = $username; 24 | $user->password = Hash::make($password); 25 | $user->save(); 26 | return json_encode(['status' => 'success']); 27 | } 28 | else 29 | { 30 | return json_encode(['status' => 'error', 'message' => 'Username already taken']); 31 | } 32 | return json_encode(['status' => 'error']); 33 | } 34 | 35 | public function login() 36 | { 37 | $validator = Validator::make(Request::all(), [ 38 | "username" => "required", 39 | "password" => "required" 40 | ]); 41 | 42 | if($validator->passes()) 43 | { 44 | $credentials = [ 45 | "username" => Request::input("username"), 46 | "password" => Request::input("password") 47 | ]; 48 | if(Auth::attempt($credentials, true)) 49 | { 50 | return json_encode(['status' => 'success', 'email' => Auth::user()->email]); 51 | } 52 | } 53 | return json_encode(['status' => 'error', 'message' => 'Invalid username or password']); 54 | } 55 | 56 | public function logout() 57 | { 58 | if(Auth::check()) 59 | { 60 | Auth::logout(); 61 | return json_encode(['status' => 'success']); 62 | } 63 | return json_encode(['status' => 'error', 'message' => 'Not logged in']); 64 | } 65 | 66 | public function change_email() 67 | { 68 | if(Auth::check()) 69 | { 70 | $user = Auth::user(); 71 | $email = Request::input('email'); 72 | if(!empty($email)) 73 | { 74 | $testUser = User::where('email', '=', $email)->first(); 75 | if($testUser && $testUser->id != $user->id) 76 | { 77 | return json_encode(['status' => 'error', 'message' => 'Email already used']); 78 | } 79 | } 80 | $user->email = $email; 81 | $user->save(); 82 | return json_encode(['status' => 'success']); 83 | } 84 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 85 | } 86 | 87 | public function change_password() 88 | { 89 | if(Auth::check()) 90 | { 91 | $user = Auth::user(); 92 | $password = Request::input('password'); 93 | $user->password = Hash::make($password); 94 | $user->save(); 95 | return json_encode(['status' => 'success']); 96 | } 97 | return json_encode(['status' => 'error', 'message' => 'Error. Logout and try again']); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/Http/Middleware/ExampleMiddleware.php: -------------------------------------------------------------------------------- 1 | post('users/register', 'App\Http\Controllers\UsersController@register'); 6 | $router->post('users/login', 'App\Http\Controllers\UsersController@login'); 7 | $router->get('users/logout', 'App\Http\Controllers\UsersController@logout'); 8 | $router->post('users/email', 'App\Http\Controllers\UsersController@change_email'); 9 | $router->post('users/password', 'App\Http\Controllers\UsersController@change_password'); 10 | $router->post('users/forgot/request', 'App\Http\Controllers\ForgotController@request'); 11 | $router->post('users/forgot/reset', 'App\Http\Controllers\ForgotController@reset'); 12 | 13 | $router->post('teams/create', 'App\Http\Controllers\TeamsController@create'); 14 | $router->post('teams/join', 'App\Http\Controllers\TeamsController@join'); 15 | $router->post('teams/leave', 'App\Http\Controllers\TeamsController@leave'); 16 | $router->post('teams/set_role', 'App\Http\Controllers\TeamsController@set_role'); 17 | $router->post('teams/remove_member', 'App\Http\Controllers\TeamsController@remove_member'); 18 | $router->post('teams/set_access_key', 'App\Http\Controllers\TeamsController@set_access_key'); 19 | $router->post('teams/list', 'App\Http\Controllers\TeamsController@list_teams'); 20 | $router->post('teams/list_members', 'App\Http\Controllers\TeamsController@list_members'); 21 | 22 | $router->post('entries/list', 'App\Http\Controllers\EntriesController@list_entries'); 23 | $router->post('entries/save', 'App\Http\Controllers\EntriesController@save'); 24 | $router->post('entries/create', 'App\Http\Controllers\EntriesController@save'); 25 | $router->post('entries/get', 'App\Http\Controllers\EntriesController@get'); 26 | $router->post('entries/vote', 'App\Http\Controllers\EntriesController@vote'); 27 | $router->post('entries/delete', 'App\Http\Controllers\EntriesController@delete'); 28 | $router->post('entries/remove_from_public', 'App\Http\Controllers\EntriesController@remove_from_public'); 29 | $router->post('entries/remove_from_teams', 'App\Http\Controllers\EntriesController@remove_from_teams'); 30 | 31 | $router->get('/', function() use ($router) 32 | { 33 | return "Nothing to see here. Move along."; 34 | }); 35 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | belongsToMany('App\Team')->withPivot('removed_from_team')->withTimestamps(); 12 | } 13 | 14 | public function user() 15 | { 16 | return $this->belongsTo('App\User'); 17 | } 18 | 19 | public function identifier() 20 | { 21 | return $this->belongsTo('App\Identifier'); 22 | } 23 | 24 | public function license() 25 | { 26 | return $this->belongsTo('App\License'); 27 | } 28 | 29 | public function votes() 30 | { 31 | return $this->hasMany('App\Vote')->withTimestamps(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Models/Identifier.php: -------------------------------------------------------------------------------- 1 | docset_name = $dict['docset_name']; 30 | $identifier->docset_filename = $dict['docset_filename']; 31 | $identifier->docset_platform = $dict['docset_platform']; 32 | $identifier->docset_bundle = $dict['docset_bundle']; 33 | $identifier->docset_version = $dict['docset_version']; 34 | $identifier->page_path = $dict['page_path']; 35 | $identifier->page_title = (array_key_exists('page_title', $dict)) ? $dict['page_title'] : ''; 36 | $identifier->httrack_source = (array_key_exists('httrack_source', $dict)) ? $dict['httrack_source'] : ''; 37 | $identifier->trim(); 38 | return $identifier; 39 | } 40 | 41 | public function trim() 42 | { 43 | $docset_filename = $this->docset_filename; 44 | $docset_filename = preg_replace('/\\.docset$/', '', $docset_filename); 45 | $docset_filename = preg_replace('/[0-9]+\.*[0-9]+(\.*[0-9]+)*/', '', $docset_filename); // remove versions 46 | $docset_filename = trim(str_replace(range(0,9),'',$docset_filename)); // remove all numbers 47 | $this->docset_filename = $docset_filename; 48 | $this->trim_apple_docset_names(); 49 | 50 | if($this->docset_filename == "Apple_API_Reference") 51 | { 52 | $this->httrack_source = str_replace("?language=objc", "", $this->httrack_source); 53 | $this->httrack_source = str_replace("/ns", "/", $this->httrack_source); 54 | $this->httrack_source = str_replace("https://", "", $this->httrack_source); 55 | $this->httrack_source = str_replace("developer.apple.com/reference/", "developer.apple.com/", $this->httrack_source); 56 | $this->httrack_source = str_replace("developer.apple.com/documentation/", "developer.apple.com/", $this->httrack_source); 57 | } 58 | $page_path = $this->page_path; 59 | $page_path = str_replace("https://", "http://", $page_path); 60 | $page_path = str_replace("swiftdoc.org/swift-2/", "swiftdoc.org/", $page_path); 61 | $basename = basename($page_path); 62 | $page_path = substr($page_path, 0, strlen($page_path)-strlen($basename)); 63 | $basename = str_replace(['-2.html', '-3.html', '-4.html', '-5.html', '-6.html', '-7.html', '-8.html', '-9.html'], '.html', $basename); 64 | $page_path = preg_replace('/v[0-9]+\.*[0-9]+(\.*[0-9]+)*/', '', $page_path); // remove versions like /v1.1.0/ 65 | $page_path = preg_replace('/[0-9]+\.*[0-9]+(\.*[0-9]+)*/', '', $page_path); // remove versions like /1.1.0/ 66 | $page_path = preg_replace('/[0-9]+_*[0-9]+(_*[0-9]+)*/', '', $page_path); // remove versions that use _ instead of . (SQLAlchemy) 67 | $page_path = str_replace(range(0,9), '', $page_path); // remove all numbers 68 | $page_path = str_replace(['/-alpha/', '/-alpha./', '/-alpha-/', '/-beta/', '/-beta./', '/-beta-/', '/-rc/', '/-rc./', '/-rc-/', '/.alpha/', '/.alpha./', '/.alpha-/', '/.beta/', '/.beta./', '/.beta-/', '/.rc/', '/.rc./', '/.rc-/'], '/', $page_path); 69 | $page_path = remove_prefix($page_path, "www."); // remove "www." for online-cloned docsets 70 | $page_path = trim(str_replace('//', '/', $page_path)); 71 | $page_path .= $basename; 72 | $this->page_path = $page_path; 73 | } 74 | 75 | public function trim_apple_docset_names() 76 | { 77 | if($this->docset_filename == 'prerelease') 78 | { 79 | if(has_prefix($this->page_path, 'ios/')) 80 | { 81 | $this->docset_filename = "com.apple.adc.documentation.iOS"; 82 | $this->page_path = substr($this->page_path, strlen('ios/')); 83 | } 84 | else if(has_prefix($this->page_path, 'mac/')) 85 | { 86 | $this->docset_filename = "com.apple.adc.documentation.OSX"; 87 | $this->page_path = substr($this->page_path, strlen('mac/')); 88 | } 89 | } 90 | else if($this->docset_filename == "ios") 91 | { 92 | $this->docset_filename = "com.apple.adc.documentation.iOS"; 93 | } 94 | else if($this->docset_filename == "mac") 95 | { 96 | $this->docset_filename = "com.apple.adc.documentation.OSX"; 97 | } 98 | else if(has_suffix($this->docset_filename, "AppleOSX.CoreReference")) 99 | { 100 | $this->docset_filename = "com.apple.adc.documentation.OSX"; 101 | } 102 | else if(has_suffix($this->docset_filename, "AppleiOS.iOSLibrary")) 103 | { 104 | $this->docset_filename = "com.apple.adc.documentation.iOS"; 105 | } 106 | } 107 | 108 | public function find_in_db() 109 | { 110 | $toMatch = ['docset_filename' => $this->docset_filename, 111 | 'page_path' => $this->page_path, 112 | ]; 113 | 114 | if($this->docset_filename == "Mono" && !empty($this->httrack_source)) 115 | { 116 | $toMatch = ['docset_filename' => $this->docset_filename, 117 | 'httrack_source' => $this->httrack_source, 118 | ]; 119 | } 120 | if($this->docset_filename == "Apple_API_Reference" && !empty($this->httrack_source)) 121 | { 122 | $toMatch = ['httrack_source' => $this->httrack_source, 123 | ]; 124 | } 125 | return Identifier::where($toMatch)->first(); 126 | } 127 | 128 | public function entries() 129 | { 130 | return $this->hasMany('App\Entry'); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/Models/License.php: -------------------------------------------------------------------------------- 1 | hasMany('App\Entry')->withTimestamps(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/Models/Team.php: -------------------------------------------------------------------------------- 1 | belongsToMany('App\User')->withPivot('role')->withTimestamps(); 12 | } 13 | 14 | public function owner() 15 | { 16 | return $this->users()->where('role', '=', 'owner')->first(); 17 | } 18 | 19 | public function entries() 20 | { 21 | return $this->belongsToMany('App\Entry')->withPivot('removed_from_team')->withTimestamps(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | notify(new ResetPasswordNotification($token)); 21 | } 22 | 23 | protected $hidden = array('password', 'remember_token'); 24 | 25 | public function teams() 26 | { 27 | return $this->belongsToMany('App\Team')->withPivot('role')->withTimestamps(); 28 | } 29 | 30 | public function entries() 31 | { 32 | return $this->hasMany('App\Entry')->withTimestamps(); 33 | } 34 | 35 | public function votes() 36 | { 37 | return $this->hasMany('App\Vote')->withTimestamps(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Models/Vote.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\User')->withTimestamps(); 10 | } 11 | 12 | public function entry() 13 | { 14 | return $this->belongsTo('App\Entry')->withTimestamps(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/Notifications/ResetPasswordNotification.php: -------------------------------------------------------------------------------- 1 | subject('Password Reset') 17 | ->view('emails.auth.reminder', ['token' => $this->token]); 18 | } 19 | } -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | make( 32 | 'Illuminate\Contracts\Console\Kernel' 33 | ); 34 | 35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput)); 36 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | load(); 12 | 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Create The Application 16 | |-------------------------------------------------------------------------- 17 | | 18 | | Here we will load the environment and create the application instance 19 | | that serves as the central piece of this framework. We'll use this 20 | | application as an "IoC" container and router for this framework. 21 | | 22 | */ 23 | 24 | $app = new Laravel\Lumen\Application( 25 | dirname(__DIR__) 26 | ); 27 | 28 | $app->withFacades(); 29 | 30 | $app->withEloquent(); 31 | 32 | // Register app configuration (encryption key) 33 | $app->configure('app'); 34 | 35 | // Register cache service 36 | $app->configure('cache'); 37 | 38 | // Register mail configuration 39 | $app->configure('mail'); 40 | $app->register(Illuminate\Mail\MailServiceProvider::class); 41 | $app->bind(Illuminate\Contracts\Mail\Factory::class, function($app) 42 | { 43 | return $app->make('mail.manager'); 44 | }); 45 | $app->register(Illuminate\Notifications\NotificationServiceProvider::class); 46 | 47 | // Register session service 48 | $app->configure('session'); 49 | $app->register(Illuminate\Session\SessionServiceProvider::class); 50 | 51 | // Register auth configuration 52 | $app->configure('auth'); 53 | $app->register(Illuminate\Auth\AuthServiceProvider::class); 54 | $app->register(Illuminate\Auth\Passwords\PasswordResetServiceProvider::class); 55 | 56 | // Register cookie service 57 | $app->singleton('cookie', function() use ($app) 58 | { 59 | return $app->loadComponent('session', 'Illuminate\Cookie\CookieServiceProvider', 'cookie'); 60 | }); 61 | 62 | $app->bind('Illuminate\Contracts\Cookie\QueueingFactory', 'cookie'); 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Register Container Bindings 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Now we will register a few bindings in the service container. We will 70 | | register the exception handler and the console kernel. You may add 71 | | your own bindings here if you like or you can make another file. 72 | | 73 | */ 74 | 75 | $app->singleton( 76 | 'Illuminate\Contracts\Debug\ExceptionHandler', 77 | 'App\Exceptions\Handler' 78 | ); 79 | 80 | $app->singleton( 81 | 'Illuminate\Contracts\Console\Kernel', 82 | 'App\Console\Kernel' 83 | ); 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Register Middleware 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Next, we will register the middleware with the application. These can 91 | | be global middleware that run before and after each request into a 92 | | route or middleware that'll be assigned to some specific routes. 93 | | 94 | */ 95 | 96 | $app->middleware([ 97 | \Illuminate\Cookie\Middleware\EncryptCookies::class, 98 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 99 | \Illuminate\Session\Middleware\StartSession::class, 100 | ]); 101 | 102 | // $app->routeMiddleware([ 103 | 104 | // ]); 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Register Service Providers 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Here we will register all of the application's service providers which 112 | | are used to bind services into the container. Service providers are 113 | | totally optional, so you are not required to uncomment this line. 114 | | 115 | */ 116 | 117 | // $app->register('App\Providers\AppServiceProvider'); 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Load The Application Routes 122 | |-------------------------------------------------------------------------- 123 | | 124 | | Next we will include the routes file so that they can all be added to 125 | | the application. This will provide all of the URLs the application 126 | | can respond to, as well as the controllers that may handle them. 127 | | 128 | */ 129 | 130 | $app->router->group([], function($router) 131 | { 132 | require __DIR__.'/../app/Http/routes.php'; 133 | }); 134 | 135 | return $app; 136 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/lumen", 3 | "description": "The Laravel Lumen Framework.", 4 | "keywords": ["framework", "laravel", "lumen"], 5 | "license": "MIT", 6 | "type": "project", 7 | "require": { 8 | "laravel/lumen-framework": "^10.0", 9 | "illuminate/mail": "^10.0", 10 | "illuminate/notifications": "^10.0", 11 | "vlucas/phpdotenv": "^5.0", 12 | "kapeli/php-markdown": "1.4.*@dev", 13 | "pear/html_safe": "dev-trunk", 14 | "ext-mbstring" : "*", 15 | "ext-tokenizer": "*", 16 | "php" : "^8.2", 17 | "illuminate/cookie": "^10.49" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^10.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "App\\": "app/" 25 | }, 26 | "classmap": [ 27 | "database/", 28 | "app/models/" 29 | ] 30 | }, 31 | "autoload-dev": { 32 | "classmap": [ 33 | "tests/" 34 | ] 35 | }, 36 | "include-path": [ 37 | "vendor/pear/xml_htmlsax3" 38 | ], 39 | "repositories": [ 40 | { 41 | "type": "vcs", 42 | "url": "https://github.com/kapeli/php-markdown" 43 | }, 44 | { 45 | "type": "vcs", 46 | "url": "https://github.com/pear/HTML_Safe" 47 | }, 48 | { 49 | "type": "package", 50 | "package": { 51 | "name": "pear/xml_htmlsax3", 52 | "version": "3.0.0", 53 | "source": { 54 | "url": "https://github.com/pear/XML_HTMLSax3", 55 | "type": "git", 56 | "reference": "origin/master" 57 | } 58 | } 59 | } 60 | ], 61 | "config": { 62 | "preferred-install": "dist", 63 | "allow-plugins": { 64 | "kylekatarnls/update-helper": true 65 | } 66 | }, 67 | "scripts": { 68 | "post-install-cmd": [ 69 | "php artisan cache:clear", 70 | "chmod -R 777 public", 71 | "chmod -R 777 storage", 72 | "php artisan migrate --force" 73 | ] 74 | }, 75 | "extra": { 76 | "heroku": { 77 | "framework": "symfony2", 78 | "document-root": "public", 79 | "index-document": "index.php", 80 | "engines": { 81 | "php": "~5.6.0", 82 | "nginx": "1.*" 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Lumen'), 12 | 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Application Environment 16 | |-------------------------------------------------------------------------- 17 | */ 18 | 19 | 'env' => env('APP_ENV', 'production'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Application Debug Mode 24 | |-------------------------------------------------------------------------- 25 | */ 26 | 27 | 'debug' => env('APP_DEBUG', false), 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Application URL 32 | |-------------------------------------------------------------------------- 33 | */ 34 | 35 | 'url' => env('APP_URL', 'http://localhost'), 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Application Timezone 40 | |-------------------------------------------------------------------------- 41 | */ 42 | 43 | 'timezone' => 'UTC', 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Application Locale Configuration 48 | |-------------------------------------------------------------------------- 49 | */ 50 | 51 | 'locale' => 'en', 52 | 53 | 'fallback_locale' => 'en', 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Encryption Key 58 | |-------------------------------------------------------------------------- 59 | | 60 | | This key is used by the Illuminate encrypter service and should be set 61 | | to a random, 32 character string, otherwise these encrypted strings 62 | | will not be safe. Please do this before deploying an application! 63 | | 64 | */ 65 | 66 | 'key' => env('APP_KEY'), 67 | 68 | 'cipher' => 'AES-256-CBC', 69 | 70 | ]; -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'guard' => 'web', 17 | 'passwords' => 'users', 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Authentication Guards 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Next, you may define every authentication guard for your application. 26 | | A default configuration has been defined here which uses session storage. 27 | | 28 | */ 29 | 30 | 'guards' => [ 31 | 'web' => [ 32 | 'driver' => 'session', 33 | 'provider' => 'users', 34 | ], 35 | ], 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | User Providers 40 | |-------------------------------------------------------------------------- 41 | | 42 | | All authentication drivers have a user provider. This defines how the 43 | | users are actually retrieved out of your database or other storage 44 | | mechanisms used by this application to persist your user's data. 45 | | 46 | */ 47 | 48 | 'providers' => [ 49 | 'users' => [ 50 | 'driver' => 'eloquent', 51 | 'model' => App\User::class, 52 | ], 53 | ], 54 | 55 | /* 56 | |-------------------------------------------------------------------------- 57 | | Password Reset Settings 58 | |-------------------------------------------------------------------------- 59 | | 60 | | Here you may set the options for resetting passwords including the view 61 | | that is your password reset e-mail. You can also set the name of the 62 | | table that maintains all of the reset tokens for your application. 63 | | 64 | */ 65 | 66 | 'passwords' => [ 67 | 'users' => [ 68 | 'provider' => 'users', 69 | 'table' => 'password_reminders', 70 | 'expire' => 60, 71 | ], 72 | ], 73 | 74 | ]; 75 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Cache Stores 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the cache "stores" for your application as 24 | | well as their drivers. You may even define multiple stores for the 25 | | same cache driver to group types of items stored in your caches. 26 | | 27 | */ 28 | 29 | 'stores' => [ 30 | 31 | 'file' => [ 32 | 'driver' => 'file', 33 | 'path' => storage_path('framework/cache/data'), 34 | ], 35 | 36 | 'memcached' => [ 37 | 'driver' => 'memcached', 38 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 39 | 'sasl' => [ 40 | env('MEMCACHED_USERNAME'), 41 | env('MEMCACHED_PASSWORD'), 42 | ], 43 | 'options' => [ 44 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 45 | ], 46 | 'servers' => [ 47 | [ 48 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 49 | 'port' => env('MEMCACHED_PORT', 11211), 50 | 'weight' => 100, 51 | ], 52 | ], 53 | ], 54 | 55 | ], 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Cache Key Prefix 60 | |-------------------------------------------------------------------------- 61 | | 62 | | When utilizing a RAM based store such as APC or Memcached, there might 63 | | be other applications utilizing the same cache. So, we'll specify a 64 | | value to get prefixed to all our keys so we can avoid collisions. 65 | | 66 | */ 67 | 68 | 'prefix' => env('CACHE_PREFIX', 'lumen_cache'), 69 | 70 | ]; -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => env('DB_CONNECTION', 'mysql'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => [ 48 | 49 | 'testing' => [ 50 | 'driver' => 'sqlite', 51 | 'database' => ':memory:', 52 | ], 53 | 54 | 'sqlite' => [ 55 | 'driver' => 'sqlite', 56 | 'database' => env('DB_DATABASE', storage_path('database.sqlite')), 57 | 'prefix' => env('DB_PREFIX', ''), 58 | ], 59 | 60 | 'mysql' => [ 61 | 'driver' => 'mysql', 62 | 'host' => env('DB_HOST', 'localhost'), 63 | 'port' => env('DB_PORT', 3306), 64 | 'database' => env('DB_DATABASE', 'forge'), 65 | 'username' => env('DB_USERNAME', 'forge'), 66 | 'password' => env('DB_PASSWORD', ''), 67 | 'charset' => 'utf8mb4', 68 | 'collation' => 'utf8mb4_unicode_ci', 69 | 'prefix' => env('DB_PREFIX', ''), 70 | 'timezone' => env('DB_TIMEZONE','+00:00'), 71 | 'strict' => false, 72 | ], 73 | 74 | 'pgsql' => [ 75 | 'driver' => 'pgsql', 76 | 'host' => env('DB_HOST', 'localhost'), 77 | 'port' => env('DB_PORT', 5432), 78 | 'database' => env('DB_DATABASE', 'forge'), 79 | 'username' => env('DB_USERNAME', 'forge'), 80 | 'password' => env('DB_PASSWORD', ''), 81 | 'charset' => 'utf8', 82 | 'prefix' => env('DB_PREFIX', ''), 83 | 'schema' => 'public', 84 | ], 85 | 86 | 'sqlsrv' => [ 87 | 'driver' => 'sqlsrv', 88 | 'host' => env('DB_HOST', 'localhost'), 89 | 'database' => env('DB_DATABASE', 'forge'), 90 | 'username' => env('DB_USERNAME', 'forge'), 91 | 'password' => env('DB_PASSWORD', ''), 92 | 'prefix' => env('DB_PREFIX', ''), 93 | ], 94 | 95 | ], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Migration Repository Table 100 | |-------------------------------------------------------------------------- 101 | | 102 | | This table keeps track of all the migrations that have already run for 103 | | your application. Using this information, we can determine which of 104 | | the migrations on disk haven't actually been run in the database. 105 | | 106 | */ 107 | 108 | 'migrations' => 'migrations', 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Redis Databases 113 | |-------------------------------------------------------------------------- 114 | | 115 | | Redis is an open source, fast, and advanced key-value store that also 116 | | provides a richer set of commands than a typical key-value systems 117 | | such as APC or Memcached. Laravel makes it easy to dig right in. 118 | | 119 | */ 120 | 121 | 'redis' => [ 122 | 123 | 'cluster' => env('REDIS_CLUSTER', false), 124 | 125 | 'default' => [ 126 | 'host' => env('REDIS_HOST', '127.0.0.1'), 127 | 'port' => env('REDIS_PORT', 6379), 128 | 'database' => env('REDIS_DATABASE', 0), 129 | 'password' => env('REDIS_PASSWORD', null) 130 | ], 131 | 132 | ], 133 | 134 | ]; -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'mail'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Mailer Configurations 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure all of the mailers used by your application plus 24 | | their respective settings. Several examples have been configured for 25 | | you and you are free to add your own as your application requires. 26 | | 27 | */ 28 | 29 | 'mailers' => [ 30 | 'mail' => [ 31 | 'transport' => 'mail', 32 | ], 33 | 34 | 'sendmail' => [ 35 | 'transport' => 'sendmail', 36 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs'), 37 | ], 38 | ], 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Global "From" Address 43 | |-------------------------------------------------------------------------- 44 | | 45 | | You may wish for all e-mails sent by your application to be sent from 46 | | the same address. Here, you may specify a name and address that is 47 | | used globally for all e-mails that are sent by your application. 48 | | 49 | */ 50 | 51 | 'from' => [ 52 | 'address' => env('MAIL_FROM_ADDRESS', 'annotations@kapeli.com'), 53 | 'name' => env('MAIL_FROM_NAME', 'Dash Annotations'), 54 | ], 55 | 56 | ]; -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'file'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Session Lifetime 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify the number of minutes that you wish the session 24 | | to be allowed to remain idle before it expires. 25 | | 26 | */ 27 | 28 | 'lifetime' => 120, 29 | 30 | 'expire_on_close' => false, 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Session Encryption 35 | |-------------------------------------------------------------------------- 36 | | 37 | | This option allows you to easily specify that all of your session data 38 | | should be encrypted before it is stored. 39 | | 40 | */ 41 | 42 | 'encrypt' => false, 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Session File Location 47 | |-------------------------------------------------------------------------- 48 | | 49 | | When using the file session driver, we need a location where session 50 | | files may be stored. A default has been set for you. 51 | | 52 | */ 53 | 54 | 'files' => storage_path('framework/sessions'), 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | Session Database Connection 59 | |-------------------------------------------------------------------------- 60 | | 61 | | When using database session driver, you may specify connection. 62 | | 63 | */ 64 | 65 | 'connection' => null, 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Session Database Table 70 | |-------------------------------------------------------------------------- 71 | | 72 | | When using database session driver, you may specify the table. 73 | | 74 | */ 75 | 76 | 'table' => 'sessions', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Session Cache Store 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When using cache-based session drivers, you may specify the cache store. 84 | | 85 | */ 86 | 87 | 'store' => null, 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | Session Sweeping Lottery 92 | |-------------------------------------------------------------------------- 93 | | 94 | | Some session drivers must manually sweep their storage location to get 95 | | rid of old sessions from storage. Here are the chances that it will 96 | | happen on a given request. By default, the odds are 2 out of 100. 97 | | 98 | */ 99 | 100 | 'lottery' => [2, 100], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Session Cookie Name 105 | |-------------------------------------------------------------------------- 106 | | 107 | | Here you may change the name of the cookie used to identify a session 108 | | instance by ID. The name specified here will get used every time a 109 | | new session cookie is created by the framework for every driver. 110 | | 111 | */ 112 | 113 | 'cookie' => env('SESSION_COOKIE', 'lumen_session'), 114 | 115 | /* 116 | |-------------------------------------------------------------------------- 117 | | Session Cookie Path 118 | |-------------------------------------------------------------------------- 119 | | 120 | | The session cookie path determines the path for which the cookie will 121 | | be regarded as available. Typically, this will be the root path. 122 | | 123 | */ 124 | 125 | 'path' => '/', 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Session Cookie Domain 130 | |-------------------------------------------------------------------------- 131 | | 132 | | Here you may change the domain of the cookie used to identify a session 133 | | in your application. This will determine which domains the cookie is 134 | | available to in your application. 135 | | 136 | */ 137 | 138 | 'domain' => env('SESSION_DOMAIN', null), 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | HTTPS Only Cookies 143 | |-------------------------------------------------------------------------- 144 | | 145 | | By setting this option to true, session cookies will only be sent back 146 | | to the server if the browser has a HTTPS connection. 147 | | 148 | */ 149 | 150 | 'secure' => env('SESSION_SECURE_COOKIE', false), 151 | 152 | /* 153 | |-------------------------------------------------------------------------- 154 | | HTTP Access Only 155 | |-------------------------------------------------------------------------- 156 | | 157 | | Setting this value to true will prevent JavaScript from accessing the 158 | | value of the cookie and the cookie will only be accessible through 159 | | the HTTP protocol. 160 | | 161 | */ 162 | 163 | 'http_only' => true, 164 | 165 | /* 166 | |-------------------------------------------------------------------------- 167 | | Same-Site Cookies 168 | |-------------------------------------------------------------------------- 169 | | 170 | | This option determines how your cookies behave when cross-site requests 171 | | take place, and can be used to mitigate CSRF attacks. By default, we 172 | | do not enable this as other CSRF protection services are in place. 173 | | 174 | */ 175 | 176 | 'same_site' => null, 177 | 178 | ]; -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kapeli/Dash-Annotations/9c575f555f75ef94776ccf07995a81b210fac280/database/migrations/.gitkeep -------------------------------------------------------------------------------- /database/migrations/2015_01_18_140441_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->string('username', 191)->unique(); 14 | $table->string('email', 300)->nullable(); 15 | $table->string('password', 500); 16 | $table->boolean('moderator')->default(FALSE); 17 | $table->string('remember_token', 500)->nullable(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::drop('users'); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/2015_01_18_155040_create_password_reminders_table.php: -------------------------------------------------------------------------------- 1 | string('email', 191)->index(); 18 | $table->string('token', 191)->index(); 19 | $table->timestamp('created_at'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('password_reminders'); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2015_01_22_150131_create_teams_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->string('name', 191)->unique(); 14 | $table->string('access_key', 500)->nullable(); 15 | $table->timestamps(); 16 | }); 17 | Schema::create('team_user', function($table) 18 | { 19 | $table->increments('id'); 20 | $table->integer('team_id')->unsigned(); 21 | $table->foreign('team_id')->references('id')->on('teams'); 22 | $table->integer('user_id')->unsigned(); 23 | $table->foreign('user_id')->references('id')->on('users'); 24 | $table->string('role'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | public function down() 30 | { 31 | Schema::drop('team_user'); 32 | Schema::drop('teams'); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2015_01_26_162713_create_entries_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->string('docset_name', 340); 14 | $table->string('docset_filename', 340); 15 | $table->string('docset_platform', 340); 16 | $table->string('docset_bundle', 340); 17 | $table->string('docset_version', 340); 18 | $table->longText('page_path'); 19 | $table->string('page_title', 340); 20 | $table->longText('httrack_source'); 21 | $table->boolean('banned_from_public')->default(FALSE); 22 | $table->timestamps(); 23 | }); 24 | 25 | Schema::create('licenses', function($table) 26 | { 27 | $table->increments('id'); 28 | $table->longText('license'); 29 | $table->boolean('banned_from_public')->default(FALSE); 30 | $table->timestamps(); 31 | }); 32 | 33 | Schema::create('entries', function($table) 34 | { 35 | $table->increments('id'); 36 | $table->string('title', 340); 37 | $table->longText('body'); 38 | $table->longText('body_rendered'); 39 | $table->string('type'); 40 | $table->integer('identifier_id')->unsigned(); 41 | $table->foreign('identifier_id')->references('id')->on('identifiers'); 42 | $table->integer('license_id')->unsigned()->nullable(); 43 | $table->foreign('license_id')->references('id')->on('licenses'); 44 | $table->string('anchor', 2000); 45 | $table->integer('user_id')->unsigned(); 46 | $table->foreign('user_id')->references('id')->on('users'); 47 | $table->boolean('public'); 48 | $table->boolean('removed_from_public')->default(FALSE); 49 | $table->integer('score'); 50 | $table->timestamps(); 51 | }); 52 | Schema::create('entry_team', function($table) 53 | { 54 | $table->increments('id'); 55 | $table->integer('entry_id')->unsigned(); 56 | $table->foreign('entry_id')->references('id')->on('entries'); 57 | $table->integer('team_id')->unsigned(); 58 | $table->foreign('team_id')->references('id')->on('teams'); 59 | $table->boolean('removed_from_team')->default(FALSE); 60 | $table->timestamps(); 61 | }); 62 | Schema::create('votes', function($table) 63 | { 64 | $table->increments('id'); 65 | $table->tinyInteger('type'); 66 | $table->integer('entry_id')->unsigned(); 67 | $table->foreign('entry_id')->references('id')->on('entries'); 68 | $table->integer('user_id')->unsigned(); 69 | $table->foreign('user_id')->references('id')->on('users'); 70 | $table->timestamps(); 71 | }); 72 | } 73 | 74 | public function down() 75 | { 76 | Schema::drop('entry_team'); 77 | Schema::drop('votes'); 78 | Schema::drop('entries'); 79 | Schema::drop('licenses'); 80 | Schema::drop('identifiers'); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('UserTableSeeder'); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: mariadb 3 | environment: 4 | - MYSQL_ROOT_PASSWORD=dash 5 | - MYSQL_USER=dash 6 | - MYSQL_PASSWORD=dash 7 | - MYSQL_DATABASE=annotations 8 | dash: 9 | build: . 10 | links: 11 | - db:db 12 | ports: 13 | - "127.0.0.1:9002:9000" 14 | environment: 15 | - TOKEN= 16 | - DB_USER=dash 17 | - DB_PASSWORD=dash 18 | - DB_DATABASE=annotations 19 | - APP_KEY= 20 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | # Useful logs for debug. 2 | rewrite_log on; 3 | 4 | # Point index to the Laravel front controller. 5 | index index.php; 6 | 7 | location / { 8 | 9 | # URLs to attempt, including pretty ones. 10 | try_files $uri $uri/ /index.php?$query_string; 11 | 12 | } 13 | 14 | # Remove trailing slash to please routing system. 15 | if (!-d $request_filename) { 16 | rewrite ^/(.+)/$ /$1 permanent; 17 | } 18 | 19 | # We don't need .ht files with nginx. 20 | location ~ /\.ht { 21 | deny all; 22 | } 23 | 24 | # Set header expirations on per-project basis 25 | location ~* \.(?:ico|css|js|jpe?g|JPG|png|svg|woff)$ { 26 | expires 365d; 27 | 28 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes... 9 | RewriteRule ^(.*)/$ /$1 [L,R=301] 10 | 11 | # Handle Front Controller... 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | RewriteCond %{REQUEST_FILENAME} !-f 14 | RewriteRule ^ index.php [L] 15 | 16 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 29 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pygments 2 | -------------------------------------------------------------------------------- /resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | "The :attribute must be accepted.", 17 | "active_url" => "The :attribute is not a valid URL.", 18 | "after" => "The :attribute must be a date after :date.", 19 | "alpha" => "The :attribute may only contain letters.", 20 | "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", 21 | "alpha_num" => "The :attribute may only contain letters and numbers.", 22 | "array" => "The :attribute must be an array.", 23 | "before" => "The :attribute must be a date before :date.", 24 | "between" => [ 25 | "numeric" => "The :attribute must be between :min and :max.", 26 | "file" => "The :attribute must be between :min and :max kilobytes.", 27 | "string" => "The :attribute must be between :min and :max characters.", 28 | "array" => "The :attribute must have between :min and :max items.", 29 | ], 30 | "boolean" => "The :attribute field must be true or false.", 31 | "confirmed" => "The :attribute confirmation does not match.", 32 | "date" => "The :attribute is not a valid date.", 33 | "date_format" => "The :attribute does not match the format :format.", 34 | "different" => "The :attribute and :other must be different.", 35 | "digits" => "The :attribute must be :digits digits.", 36 | "digits_between" => "The :attribute must be between :min and :max digits.", 37 | "email" => "The :attribute must be a valid email address.", 38 | "filled" => "The :attribute field is required.", 39 | "exists" => "The selected :attribute is invalid.", 40 | "image" => "The :attribute must be an image.", 41 | "in" => "The selected :attribute is invalid.", 42 | "integer" => "The :attribute must be an integer.", 43 | "ip" => "The :attribute must be a valid IP address.", 44 | "max" => [ 45 | "numeric" => "The :attribute may not be greater than :max.", 46 | "file" => "The :attribute may not be greater than :max kilobytes.", 47 | "string" => "The :attribute may not be greater than :max characters.", 48 | "array" => "The :attribute may not have more than :max items.", 49 | ], 50 | "mimes" => "The :attribute must be a file of type: :values.", 51 | "min" => [ 52 | "numeric" => "The :attribute must be at least :min.", 53 | "file" => "The :attribute must be at least :min kilobytes.", 54 | "string" => "The :attribute must be at least :min characters.", 55 | "array" => "The :attribute must have at least :min items.", 56 | ], 57 | "not_in" => "The selected :attribute is invalid.", 58 | "numeric" => "The :attribute must be a number.", 59 | "regex" => "The :attribute format is invalid.", 60 | "required" => "The :attribute field is required.", 61 | "required_if" => "The :attribute field is required when :other is :value.", 62 | "required_with" => "The :attribute field is required when :values is present.", 63 | "required_with_all" => "The :attribute field is required when :values is present.", 64 | "required_without" => "The :attribute field is required when :values is not present.", 65 | "required_without_all" => "The :attribute field is required when none of :values are present.", 66 | "same" => "The :attribute and :other must match.", 67 | "size" => [ 68 | "numeric" => "The :attribute must be :size.", 69 | "file" => "The :attribute must be :size kilobytes.", 70 | "string" => "The :attribute must be :size characters.", 71 | "array" => "The :attribute must contain :size items.", 72 | ], 73 | "unique" => "The :attribute has already been taken.", 74 | "url" => "The :attribute format is invalid.", 75 | "timezone" => "The :attribute must be a valid zone.", 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Custom Validation Language Lines 80 | |-------------------------------------------------------------------------- 81 | | 82 | | Here you may specify custom validation messages for attributes using the 83 | | convention "attribute.rule" to name the lines. This makes it quick to 84 | | specify a specific custom language line for a given attribute rule. 85 | | 86 | */ 87 | 88 | 'custom' => [ 89 | 'attribute-name' => [ 90 | 'rule-name' => 'custom-message', 91 | ], 92 | ], 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Custom Validation Attributes 97 | |-------------------------------------------------------------------------- 98 | | 99 | | The following language lines are used to swap attribute place-holders 100 | | with something more reader friendly such as E-Mail Address instead 101 | | of "email". This simply helps us make messages a little cleaner. 102 | | 103 | */ 104 | 105 | 'attributes' => [], 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kapeli/Dash-Annotations/9c575f555f75ef94776ccf07995a81b210fac280/resources/views/.gitkeep -------------------------------------------------------------------------------- /resources/views/emails/auth/reminder.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Your reset token is: {{{ $token }}}

9 |

This token will expire in 60 minutes.

10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.9 2 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | call('GET', '/'); 13 | 14 | $this->assertResponseOk(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |